From fd045359fa4fce8a6264bbfd0455eb674982fbdf Mon Sep 17 00:00:00 2001 From: LostLuma Date: Tue, 23 Apr 2024 02:45:13 +0200 Subject: [PATCH 01/23] Use different User Agent than upstream --- src/main/java/com/terraformersmc/modmenu/ModMenu.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/terraformersmc/modmenu/ModMenu.java b/src/main/java/com/terraformersmc/modmenu/ModMenu.java index a7cbeed8..3b889726 100644 --- a/src/main/java/com/terraformersmc/modmenu/ModMenu.java +++ b/src/main/java/com/terraformersmc/modmenu/ModMenu.java @@ -31,7 +31,7 @@ public class ModMenu implements ClientModInitializer { public static final String MOD_ID = "modmenu"; - public static final String GITHUB_REF = "TerraformersMC/ModMenu"; + public static final String GITHUB_REF = "OrnitheMC/ModMenu"; public static final Logger LOGGER = LogManager.getLogger("Mod Menu"); public static final Gson GSON = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).setPrettyPrinting().create(); public static final Gson GSON_MINIFIED = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); From cc7e97b9dafa9f3432ed0440a431897bf55f61a3 Mon Sep 17 00:00:00 2001 From: Bloodaxe <36518086+Bloodaxe95@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:47:04 +0100 Subject: [PATCH 02/23] =?UTF-8?q?Updated=20Norwegian=20Bokm=C3=A5l=20trans?= =?UTF-8?q?lations=20(no=5Fno.json)=20(#656)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated Norwegian Bokmål translations --- .../resources/assets/modmenu/lang/no_NO.lang | 231 +++++++++--------- 1 file changed, 120 insertions(+), 111 deletions(-) diff --git a/src/main/resources/assets/modmenu/lang/no_NO.lang b/src/main/resources/assets/modmenu/lang/no_NO.lang index 71f054fa..4a2c4e6d 100644 --- a/src/main/resources/assets/modmenu/lang/no_NO.lang +++ b/src/main/resources/assets/modmenu/lang/no_NO.lang @@ -1,68 +1,70 @@ category.modmenu.name=Mod Menu -key.modmenu.open_menu=Open Mod Menu -modmenu.title=Mods -modmenu.nameTranslation.modmenu=Mod Menu -modmenu.descriptionTranslation.modmenu=Adds a mod menu to view the list of mods you have installed. -modmenu.loaded=(%s Loaded) +key.modmenu.open_menu=Åpne moddmeny +modmenu.title=Modifikasjoner +"modmenu.nameTranslation.modmenu=Modifikasjonsmeny +modmenu.descriptionTranslation.modmenu=Legger til en meny for modifikasjoner, for å vise en liste over modder du har installert. +modmenu.loaded=(%s Lastet) modmenu.loaded.short=(%s) -modmenu.loaded.69.secret=(%s Loaded...nice) -modmenu.loaded.420.secret=(%s Loaded...blaze it) -modmenu.mods.n= (%s Mods) +modmenu.loaded.69.secret=(%s Lastet...nice) +modmenu.loaded.420.secret=(%s Lastet...blaze it) +modmenu.mods.n= (%s Modder) modmenu.mods.1= (%s Mod) -modmenu.mods.69.secret= (%s Mods...nice) -modmenu.mods.420.secret= (%s Mods...blaze it) -modmenu.search=Search for mods -modmenu.searchTerms.library=api library +modmenu.mods.69.secret= (%s Modder...nice) +modmenu.mods.420.secret= (%s Modder...blaze it) +modmenu.search=Søk etter modder +modmenu.searchTerms.library=api bibliotek modmenu.searchTerms.patchwork=patchwork forge fml modmenu.searchTerms.modpack=modpack pack modmenu.searchTerms.deprecated=deprecated outdated old modmenu.searchTerms.clientside=clientside gameside modmenu.searchTerms.configurable=configurations configs configures configurable options -modmenu.toggleFilterOptions=Toggle Filter Options -modmenu.showingMods.n=Showing %s mods -modmenu.showingMods.1=Showing %s mod -modmenu.showingLibraries.n=Showing %s libraries -modmenu.showingLibraries.1=Showing %s library -modmenu.showingModsLibraries.n.n=Showing %s mods and %s libraries -modmenu.showingModsLibraries.n.1=Showing %s mods and %s library -modmenu.showingModsLibraries.1.n=Showing %s mod and %s libraries -modmenu.showingModsLibraries.1.1=Showing %s mod and %s library -modmenu.badge.library=Library -modmenu.badge.clientsideOnly=Client -modmenu.badge.deprecated=Deprecated +modmenu.searchTerms.hasUpdate=oppdateringer versjon +modmenu.toggleFilterOptions=Veksle filteralternativer +modmenu.showingMods.n=Viser %s modder +modmenu.showingMods.1=Viser %s mod +modmenu.showingLibraries.n=Viser %s biblioteker +modmenu.showingLibraries.1=Viser %s bibliotek +modmenu.showingModsLibraries.n.n=Viser %s modder og %s biblioteker +modmenu.showingModsLibraries.n.1=Viser %s modder og %s bibliotek +modmenu.showingModsLibraries.1.n=Viser %s mod og %s biblioteker +modmenu.showingModsLibraries.1.1=Viser %s mod og %s bibliotek +modmenu.badge.library=Bibliotek +modmenu.badge.clientsideOnly=Klient +modmenu.badge.deprecated=Foreldet modmenu.badge.forge=Forge -modmenu.badge.modpack=Modpack +modmenu.badge.modpack=Modpakke modmenu.badge.minecraft=Minecraft -modmenu.dropInfo.line1=Drag and drop files into -modmenu.dropInfo.line2=this window to add mods -modmenu.dropConfirm=Do you want to copy the following mods into the mods directory? -modmenu.dropSuccessful.line1=Successfully copied mods -modmenu.dropSuccessful.line2=Restart game to load mods +modmenu.dropInfo.line1=Dra og slipp filer i dette vinduet +modmenu.dropInfo.line2=for å legge til modifikasjoner +modmenu.dropConfirm=Vil du kopiere de følgende moddene til modsmappen? +modmenu.dropSuccessful.line1=Klarte å kopiere modder +modmenu.dropSuccessful.line2=Restart spillet for å laste inn modder modmenu.modIdToolTip=Mod ID: %s -modmenu.authorPrefix=By %s -modmenu.config=Edit Config -modmenu.configure=Configure... -modmenu.configure.error=Failed to load config screen for '%s' -Report to '%s', not Mod Menu -modmenu.website=Website -modmenu.issues=Issues -modmenu.credits=Credits: -modmenu.viewCredits=View Credits -modmenu.license=License: -modmenu.links=Links: -modmenu.source=Source -modmenu.hasUpdate=An update is available: -modmenu.childHasUpdate=A child of this mod has an update available. -modmenu.updateText=v%s on %s +modmenu.authorPrefix=Av %s +modmenu.config=Endre konfigurasjon +modmenu.configure=Konfigurer... +modmenu.configure.error=Kunne ikke laste inn konfigurasjonsskjermen for '%s' +Rapporter til '%s', ingen mod-meny +modmenu.website=Nettsted +modmenu.issues=Problemer +modmenu.credits=Medvirkende: +modmenu.viewCredits=Vis medvirkende +modmenu.license=Lisens: +modmenu.links=Lenker: +modmenu.source=Kilde +modmenu.hasUpdate=En oppdatering er tilgjengelig: +modmenu.experimental=(Mod-menyens oppdaterings-sjekker er eksperimentell!) +modmenu.childHasUpdate=Et barn av denne modden har en oppdatering tilgjengelig. +modmenu.updateText=v%s på %s modmenu.buymeacoffee=Buy Me a Coffee modmenu.coindrop=Coindrop modmenu.crowdin=Crowdin modmenu.curseforge=CurseForge modmenu.discord=Discord -modmenu.donate=Donate +modmenu.donate=Doner modmenu.flattr=Flattr -modmenu.github_releases=GitHub Releases -modmenu.github_sponsors=GitHub Sponsors +modmenu.github_releases=GitHub utgivelser +modmenu.github_sponsors=GitHub sponsorer modmenu.kofi=Ko-fi modmenu.liberapay=Liberapay modmenu.mastodon=Mastodon @@ -75,72 +77,79 @@ modmenu.twitch=Twitch modmenu.twitter=Twitter modmenu.wiki=Wiki modmenu.youtube=YouTube -modmenu.modsFolder=Open Mods Folder -modmenu.configsFolder=Open Configs Folder +modmenu.modsFolder=Åpne mappe for modder +modmenu.configsFolder=Åpne mappe for konfigurasjonsfiler modmenu.nameTranslation.minecraft=Minecraft -modmenu.descriptionTranslation.minecraft=The base game. +modmenu.descriptionTranslation.minecraft=Grunnspillet. modmenu.nameTranslation.java=Java -modmenu.descriptionTranslation.java=The Java runtime environment. -modmenu.javaDistributionName=Running: %s -modmenu.options=Mod Menu Options -option.modmenu.sorting=Sort +modmenu.descriptionTranslation.java=Java runtime miljøet. +modmenu.javaDistributionName=Kjører: %s +modmenu.options=Instillinger for modmeny +option.modmenu.sorting=Sorter option.modmenu.sorting.ascending=A-Z option.modmenu.sorting.descending=Z-A -option.modmenu.show_libraries=Libraries -option.modmenu.show_libraries.true=Shown -option.modmenu.show_libraries.false=Hidden -option.modmenu.hide_config_buttons=Config Buttons -option.modmenu.hide_config_buttons.true=Hidden -option.modmenu.hide_config_buttons.false=Shown -option.modmenu.hide_badges=Mod Badges -option.modmenu.hide_badges.true=Hidden -option.modmenu.hide_badges.false=Shown -option.modmenu.compact_list=List -option.modmenu.compact_list.true=Compact +option.modmenu.show_libraries=Biblioteker +option.modmenu.show_libraries.true=Synlig +option.modmenu.show_libraries.false=Skjult +option.modmenu.hide_config_buttons=Konfigurasjonsknapper +option.modmenu.hide_config_buttons.true=Skjult +option.modmenu.hide_config_buttons.false=Synlig +option.modmenu.hide_badges=Mod medaljer +option.modmenu.hide_badges.true=Skjult +option.modmenu.hide_badges.false=Synlig +option.modmenu.compact_list=Liste +option.modmenu.compact_list.true=Kompakt option.modmenu.compact_list.false=Standard -option.modmenu.hide_mod_links=Mod Links -option.modmenu.hide_mod_links.true=Hidden -option.modmenu.hide_mod_links.false=Shown -option.modmenu.hide_mod_credits=Mod Credits -option.modmenu.hide_mod_credits.true=Hidden -option.modmenu.hide_mod_credits.false=Shown -option.modmenu.hide_mod_license=Mod License -option.modmenu.hide_mod_license.true=Hidden -option.modmenu.hide_mod_license.false=Shown -option.modmenu.count_children=Children -option.modmenu.count_children.true=Counted -option.modmenu.count_children.false=Not Counted -option.modmenu.count_libraries=Libraries -option.modmenu.count_libraries.true=Counted -option.modmenu.count_libraries.false=Not Counted -option.modmenu.count_hidden_mods=Hidden Mods -option.modmenu.count_hidden_mods.true=Counted -option.modmenu.count_hidden_mods.false=Not Counted -option.modmenu.mod_count_location=Mod Count -option.modmenu.mod_count_location.title_screen=Title Corner -option.modmenu.mod_count_location.mods_button=Mods Button -option.modmenu.mod_count_location.title_screen_and_mods_button=Both -option.modmenu.mod_count_location.none=Neither +option.modmenu.hide_mod_links=Modlenker +option.modmenu.hide_mod_links.true=Skjult +option.modmenu.hide_mod_links.false=Synlig +option.modmenu.hide_mod_credits=Mod Medvirkende +option.modmenu.hide_mod_credits.true=Skjult +option.modmenu.hide_mod_credits.false=Synlig +option.modmenu.hide_mod_license=Mod lisens +option.modmenu.hide_mod_license.true=Skjult +option.modmenu.hide_mod_license.false=Synlig +option.modmenu.count_children=Barn +option.modmenu.count_children.true=Telles +option.modmenu.count_children.false=Telles ikke +option.modmenu.count_libraries=Biblioteker +option.modmenu.count_libraries.true=Telles +option.modmenu.count_libraries.false=Telles ikke +option.modmenu.count_hidden_mods=Skjulte modder +option.modmenu.count_hidden_mods.true=Telles +option.modmenu.count_hidden_mods.false=Telles ikke +option.modmenu.mod_count_location=Antall modder +option.modmenu.mod_count_location.title_screen=Hjørne av tittelskjerm +option.modmenu.mod_count_location.mods_button=Knappen for modder +option.modmenu.mod_count_location.title_screen_and_mods_button=Begge +option.modmenu.mod_count_location.none=Ingen option.modmenu.easter_eggs=Easter Eggs -option.modmenu.easter_eggs.true=Enabled -option.modmenu.easter_eggs.false=Disabled -option.modmenu.mods_button_style=Mods Button -option.modmenu.mods_button_style.classic=Below Realms -option.modmenu.mods_button_style.replace_realms=Replace Realms -option.modmenu.mods_button_style.shrink=Adjacent -option.modmenu.mods_button_style.icon=Icon -option.modmenu.random_java_colors=Java Color -option.modmenu.random_java_colors.true=Vendor -option.modmenu.random_java_colors.false=Always Red -option.modmenu.translate_names=Names -option.modmenu.translate_names.true=Localized -option.modmenu.translate_names.false=Not Localized -option.modmenu.translate_descriptions=Descriptions -option.modmenu.translate_descriptions.true=Localized -option.modmenu.translate_descriptions.false=Not Localized -option.modmenu.update_checker=Update Checker -option.modmenu.update_checker.true=Enabled -option.modmenu.update_checker.false=Disabled -option.modmenu.button_update_badge=Update Indicator -option.modmenu.button_update_badge.true=Shown -option.modmenu.button_update_badge.false=Hidden +option.modmenu.easter_eggs.true=På +option.modmenu.easter_eggs.false=Av +option.modmenu.mods_button_style=Knapp for modder +option.modmenu.mods_button_style.classic=Under Realms +option.modmenu.mods_button_style.replace_realms=Erstatter Realms +option.modmenu.mods_button_style.shrink=Ved siden av +option.modmenu.mods_button_style.icon=Ikon +option.modmenu.game_menu_button_style=Spillmeny +option.modmenu.game_menu_button_style.below_bugs=Under feilmeldinger +option.modmenu.game_menu_button_style.replace_bugs=Erstatt feilmeldinger +option.modmenu.game_menu_button_style.icon=Ikon +option.modmenu.random_java_colors=Java Farge +option.modmenu.random_java_colors.true=Leverandør +option.modmenu.random_java_colors.false=Alltid rød +option.modmenu.translate_names=Navn +option.modmenu.translate_names.true=Oversatt +option.modmenu.translate_names.false=Ikke oversatt +option.modmenu.translate_descriptions=Beskrivelser +option.modmenu.translate_descriptions.true=Oversatt +option.modmenu.translate_descriptions.false=Ikke oversatt +option.modmenu.update_checker=Oppdaterings-sjekker +option.modmenu.update_checker.true=Aktivert +option.modmenu.update_checker.false=Deaktivert +option.modmenu.button_update_badge=Oppdateringsindikator +option.modmenu.button_update_badge.true=Synlig +option.modmenu.button_update_badge.false=Skjult +option.modmenu.quick_configure=Hurtigkonfigurering +option.modmenu.quick_configure.true=Aktivert +option.modmenu.quick_configure.false=Deaktivert From 2569b0b907402286d5c1e49ec9ce1e3e7f474091 Mon Sep 17 00:00:00 2001 From: aotusoft <51018049+aotusoft@users.noreply.github.com> Date: Thu, 30 Nov 2023 02:47:26 +0800 Subject: [PATCH 03/23] Update zh_cn.json (#655) - Updated Chinese translations --- .../resources/assets/modmenu/lang/zh_CN.lang | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main/resources/assets/modmenu/lang/zh_CN.lang b/src/main/resources/assets/modmenu/lang/zh_CN.lang index 711488e6..47d5d3bb 100644 --- a/src/main/resources/assets/modmenu/lang/zh_CN.lang +++ b/src/main/resources/assets/modmenu/lang/zh_CN.lang @@ -1,3 +1,4 @@ +{ category.modmenu.name=模组菜单 key.modmenu.open_menu=打开模组菜单 modmenu.title=模组 @@ -18,6 +19,7 @@ modmenu.searchTerms.modpack=模组整合包 modmenu.searchTerms.deprecated=已过时且被弃用的旧版本 modmenu.searchTerms.clientside=客户端游戏端 modmenu.searchTerms.configurable=配置 配置 配置 可配置 选项 +modmenu.searchTerms.hasUpdate=更新版本 modmenu.toggleFilterOptions=切换过滤选项 modmenu.showingMods.n=当前显示%s个模组 modmenu.showingMods.1=当前显示%s个模组 @@ -52,6 +54,7 @@ modmenu.license=许可证: modmenu.links=链接: modmenu.source=源码 modmenu.hasUpdate=有更新可用: +modmenu.experimental=(更新检测器处于实验阶段!) modmenu.childHasUpdate=此模组的一个子模组有更新。 modmenu.updateText=%2$s中的v%1$s modmenu.buymeacoffee=给我买一杯咖啡 @@ -60,7 +63,7 @@ modmenu.crowdin=Crowdin modmenu.curseforge=CurseForge modmenu.discord=Discord modmenu.donate=捐助 -modmenu.flattr=弗拉特 +modmenu.flattr=Flattr modmenu.github_releases=GitHub 发行版 modmenu.github_sponsors=GitHub 赞助者 modmenu.kofi=Ko-fi @@ -80,7 +83,7 @@ modmenu.configsFolder=打开配置文件夹 modmenu.nameTranslation.minecraft=Minecraft modmenu.descriptionTranslation.minecraft=基础游戏。 modmenu.nameTranslation.java=Java -modmenu.descriptionTranslation.java=Java运行环境。 +modmenu.descriptionTranslation.java=Java运行时环境 modmenu.javaDistributionName=正在运行:%s modmenu.options=模组菜单选项 option.modmenu.sorting=排序方式 @@ -124,23 +127,30 @@ option.modmenu.mod_count_location.none=都不显示 option.modmenu.easter_eggs=彩蛋 option.modmenu.easter_eggs.true=启用 option.modmenu.easter_eggs.false=禁用 -option.modmenu.mods_button_style=模组按钮位置 +option.modmenu.mods_button_style=主菜单中的位置 option.modmenu.mods_button_style.classic=Realms下一行 option.modmenu.mods_button_style.replace_realms=替换掉Realms option.modmenu.mods_button_style.shrink=Realms同一行 -option.modmenu.mods_button_style.icon=Realms边的图标 -option.modmenu.random_java_colors=Java颜色 -option.modmenu.random_java_colors.true=供应商 +option.modmenu.mods_button_style.icon=图标 +option.modmenu.game_menu_button_style=暂停菜单中的位置 +option.modmenu.game_menu_button_style.below_bugs=Bugs下一行 +option.modmenu.game_menu_button_style.replace_bugs=替换掉Bugs +option.modmenu.game_menu_button_style.icon=图标 +option.modmenu.random_java_colors=Java图标颜色 +option.modmenu.random_java_colors.true=供应商颜色 option.modmenu.random_java_colors.false=总是红色 option.modmenu.translate_names=名称 option.modmenu.translate_names.true=本地化 -option.modmenu.translate_names.false=未本地化 +option.modmenu.translate_names.false=非本地化 option.modmenu.translate_descriptions=描述 option.modmenu.translate_descriptions.true=本地化 -option.modmenu.translate_descriptions.false=未本地化 +option.modmenu.translate_descriptions.false=非本地化 option.modmenu.update_checker=更新检测器 option.modmenu.update_checker.true=启用 option.modmenu.update_checker.false=禁用 option.modmenu.button_update_badge=更新指示器 option.modmenu.button_update_badge.true=显示 option.modmenu.button_update_badge.false=隐藏 +option.modmenu.quick_configure=快速配置 +option.modmenu.quick_configure.true=启用 +option.modmenu.quick_configure.false=禁用 From e1e4b6675986712cf4a728e63f1414c9ec90e50b Mon Sep 17 00:00:00 2001 From: Alex Gazmanovich <128359229+Gazmanovich@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:49:01 +0300 Subject: [PATCH 04/23] Update be_by.json (#654) - Updated Belarusian translations --- .../resources/assets/modmenu/lang/be_BY.lang | 292 +++++++++--------- 1 file changed, 146 insertions(+), 146 deletions(-) diff --git a/src/main/resources/assets/modmenu/lang/be_BY.lang b/src/main/resources/assets/modmenu/lang/be_BY.lang index 71f054fa..bc0cf954 100644 --- a/src/main/resources/assets/modmenu/lang/be_BY.lang +++ b/src/main/resources/assets/modmenu/lang/be_BY.lang @@ -1,146 +1,146 @@ -category.modmenu.name=Mod Menu -key.modmenu.open_menu=Open Mod Menu -modmenu.title=Mods -modmenu.nameTranslation.modmenu=Mod Menu -modmenu.descriptionTranslation.modmenu=Adds a mod menu to view the list of mods you have installed. -modmenu.loaded=(%s Loaded) -modmenu.loaded.short=(%s) -modmenu.loaded.69.secret=(%s Loaded...nice) -modmenu.loaded.420.secret=(%s Loaded...blaze it) -modmenu.mods.n= (%s Mods) -modmenu.mods.1= (%s Mod) -modmenu.mods.69.secret= (%s Mods...nice) -modmenu.mods.420.secret= (%s Mods...blaze it) -modmenu.search=Search for mods -modmenu.searchTerms.library=api library -modmenu.searchTerms.patchwork=patchwork forge fml -modmenu.searchTerms.modpack=modpack pack -modmenu.searchTerms.deprecated=deprecated outdated old -modmenu.searchTerms.clientside=clientside gameside -modmenu.searchTerms.configurable=configurations configs configures configurable options -modmenu.toggleFilterOptions=Toggle Filter Options -modmenu.showingMods.n=Showing %s mods -modmenu.showingMods.1=Showing %s mod -modmenu.showingLibraries.n=Showing %s libraries -modmenu.showingLibraries.1=Showing %s library -modmenu.showingModsLibraries.n.n=Showing %s mods and %s libraries -modmenu.showingModsLibraries.n.1=Showing %s mods and %s library -modmenu.showingModsLibraries.1.n=Showing %s mod and %s libraries -modmenu.showingModsLibraries.1.1=Showing %s mod and %s library -modmenu.badge.library=Library -modmenu.badge.clientsideOnly=Client -modmenu.badge.deprecated=Deprecated -modmenu.badge.forge=Forge -modmenu.badge.modpack=Modpack -modmenu.badge.minecraft=Minecraft -modmenu.dropInfo.line1=Drag and drop files into -modmenu.dropInfo.line2=this window to add mods -modmenu.dropConfirm=Do you want to copy the following mods into the mods directory? -modmenu.dropSuccessful.line1=Successfully copied mods -modmenu.dropSuccessful.line2=Restart game to load mods -modmenu.modIdToolTip=Mod ID: %s -modmenu.authorPrefix=By %s -modmenu.config=Edit Config -modmenu.configure=Configure... -modmenu.configure.error=Failed to load config screen for '%s' -Report to '%s', not Mod Menu -modmenu.website=Website -modmenu.issues=Issues -modmenu.credits=Credits: -modmenu.viewCredits=View Credits -modmenu.license=License: -modmenu.links=Links: -modmenu.source=Source -modmenu.hasUpdate=An update is available: -modmenu.childHasUpdate=A child of this mod has an update available. -modmenu.updateText=v%s on %s -modmenu.buymeacoffee=Buy Me a Coffee -modmenu.coindrop=Coindrop -modmenu.crowdin=Crowdin -modmenu.curseforge=CurseForge -modmenu.discord=Discord -modmenu.donate=Donate -modmenu.flattr=Flattr -modmenu.github_releases=GitHub Releases -modmenu.github_sponsors=GitHub Sponsors -modmenu.kofi=Ko-fi -modmenu.liberapay=Liberapay -modmenu.mastodon=Mastodon -modmenu.modrinth=Modrinth -modmenu.opencollective=Open Collective -modmenu.patreon=Patreon -modmenu.paypal=PayPal -modmenu.reddit=Reddit -modmenu.twitch=Twitch -modmenu.twitter=Twitter -modmenu.wiki=Wiki -modmenu.youtube=YouTube -modmenu.modsFolder=Open Mods Folder -modmenu.configsFolder=Open Configs Folder -modmenu.nameTranslation.minecraft=Minecraft -modmenu.descriptionTranslation.minecraft=The base game. -modmenu.nameTranslation.java=Java -modmenu.descriptionTranslation.java=The Java runtime environment. -modmenu.javaDistributionName=Running: %s -modmenu.options=Mod Menu Options -option.modmenu.sorting=Sort -option.modmenu.sorting.ascending=A-Z -option.modmenu.sorting.descending=Z-A -option.modmenu.show_libraries=Libraries -option.modmenu.show_libraries.true=Shown -option.modmenu.show_libraries.false=Hidden -option.modmenu.hide_config_buttons=Config Buttons -option.modmenu.hide_config_buttons.true=Hidden -option.modmenu.hide_config_buttons.false=Shown -option.modmenu.hide_badges=Mod Badges -option.modmenu.hide_badges.true=Hidden -option.modmenu.hide_badges.false=Shown -option.modmenu.compact_list=List -option.modmenu.compact_list.true=Compact -option.modmenu.compact_list.false=Standard -option.modmenu.hide_mod_links=Mod Links -option.modmenu.hide_mod_links.true=Hidden -option.modmenu.hide_mod_links.false=Shown -option.modmenu.hide_mod_credits=Mod Credits -option.modmenu.hide_mod_credits.true=Hidden -option.modmenu.hide_mod_credits.false=Shown -option.modmenu.hide_mod_license=Mod License -option.modmenu.hide_mod_license.true=Hidden -option.modmenu.hide_mod_license.false=Shown -option.modmenu.count_children=Children -option.modmenu.count_children.true=Counted -option.modmenu.count_children.false=Not Counted -option.modmenu.count_libraries=Libraries -option.modmenu.count_libraries.true=Counted -option.modmenu.count_libraries.false=Not Counted -option.modmenu.count_hidden_mods=Hidden Mods -option.modmenu.count_hidden_mods.true=Counted -option.modmenu.count_hidden_mods.false=Not Counted -option.modmenu.mod_count_location=Mod Count -option.modmenu.mod_count_location.title_screen=Title Corner -option.modmenu.mod_count_location.mods_button=Mods Button -option.modmenu.mod_count_location.title_screen_and_mods_button=Both -option.modmenu.mod_count_location.none=Neither -option.modmenu.easter_eggs=Easter Eggs -option.modmenu.easter_eggs.true=Enabled -option.modmenu.easter_eggs.false=Disabled -option.modmenu.mods_button_style=Mods Button -option.modmenu.mods_button_style.classic=Below Realms -option.modmenu.mods_button_style.replace_realms=Replace Realms -option.modmenu.mods_button_style.shrink=Adjacent -option.modmenu.mods_button_style.icon=Icon -option.modmenu.random_java_colors=Java Color -option.modmenu.random_java_colors.true=Vendor -option.modmenu.random_java_colors.false=Always Red -option.modmenu.translate_names=Names -option.modmenu.translate_names.true=Localized -option.modmenu.translate_names.false=Not Localized -option.modmenu.translate_descriptions=Descriptions -option.modmenu.translate_descriptions.true=Localized -option.modmenu.translate_descriptions.false=Not Localized -option.modmenu.update_checker=Update Checker -option.modmenu.update_checker.true=Enabled -option.modmenu.update_checker.false=Disabled -option.modmenu.button_update_badge=Update Indicator -option.modmenu.button_update_badge.true=Shown -option.modmenu.button_update_badge.false=Hidden +category.modmenu.nameMod Menu +key.modmenu.open_menuАдкрыць Mod Menu +modmenu.titleМоды +modmenu.nameTranslation.modmenuMod Menu +modmenu.descriptionTranslation.modmenuДадае меню модаў для прагляду спіса модаў, якія ў вас усталяваны. +modmenu.loaded(%s загружана) +modmenu.loaded.short(%s) +modmenu.loaded.69.secret(%s загружана...файна) +modmenu.loaded.420.secret(%s загружана...трасца) +modmenu.mods.n (%s моды) +modmenu.mods.1 (%s мод) +modmenu.mods.69.secret (%s модаў...файна) +modmenu.mods.420.secret (%s модаў...трасца) +modmenu.searchПошук модаў +modmenu.searchTerms.libraryAPI бібліятэка +modmenu.searchTerms.patchworkpatchwork forge fml +modmenu.searchTerms.modpackзборка зборка +modmenu.searchTerms.deprecatedсастарэлы неактуальны стары +modmenu.searchTerms.clientsideбок-кліента бок-гульні +modmenu.searchTerms.configurableНалады наладжваемый насладжваемыя опцыі +modmenu.toggleFilterOptionsУключыць фільтр +modmenu.showingMods.nПаказваецца %s модаў +modmenu.showingMods.1паказваецца %s мод +modmenu.showingLibraries.nПаказана %s бібліятэк +modmenu.showingLibraries.1Паказана %s бібліятэка +modmenu.showingModsLibraries.n.nПаказана %s модаў і %s бібліятэк +modmenu.showingModsLibraries.n.1Паказана %s модаў і %s бібліятэка +modmenu.showingModsLibraries.1.nПаказана %s мод і %s бібліятэк +modmenu.showingModsLibraries.1.1Паказана %s мод і %s бібліятэка +modmenu.badge.libraryБібліятэка +modmenu.badge.clientsideOnlyКліент +modmenu.badge.deprecatedСастарэлы +modmenu.badge.forgeForge +modmenu.badge.modpackЗборка +modmenu.badge.minecraftMinecraft +modmenu.dropInfo.line1Перацягніце файлу ў +modmenu.dropInfo.line2гэта акно, каб дадаць моды +modmenu.dropConfirmВы хаціце скапіяваць наступныя моды у вашу папку mods? +modmenu.dropSuccessful.line1Моды скапіяваны паспяхова +modmenu.dropSuccessful.line2Перазапусціце гульню, каб загрузіць моды +modmenu.modIdToolTipМод ID: %s +modmenu.authorPrefixад %s +modmenu.configЗмяніць налады +modmenu.configureНаладзіць... +modmenu.configure.errorНемагчыма загрузіць экран налад для '%s' +Паведаміце '%s', не Mod Menu +modmenu.websiteСайт +modmenu.issuesПажаданні +modmenu.creditsАўтары: +modmenu.viewCreditsПаглядзець аўтараў +modmenu.licenseЛіцэнзія: +modmenu.linksСпасылкі: +modmenu.sourceКрыінца +modmenu.hasUpdateДаступна абнаўленне: +modmenu.childHasUpdateАбнаўленне дзіцячага мода даступна. +modmenu.updateTextv%s на %s +modmenu.buymeacoffeeBuy Me a Coffee +modmenu.coindropCoindrop +modmenu.crowdinCrowdin +modmenu.curseforgeCurseForge +modmenu.discordDiscord +modmenu.donateDonate +modmenu.flattrFlattr +modmenu.github_releasesGitHub рэлізы +modmenu.github_sponsorsGitHub спонсары +modmenu.kofiKo-fi +modmenu.liberapayLiberapay +modmenu.mastodonMastodon +modmenu.modrinthModrinth +modmenu.opencollectiveOpen Collective +modmenu.patreonPatreon +modmenu.paypalPayPal +modmenu.redditReddit +modmenu.twitchTwitch +modmenu.twitterTwitter +modmenu.wikiWiki +modmenu.youtubeYouTube +modmenu.modsFolderАдкрыць папку модаў +modmenu.configsFolderАдкрыць папку налад +modmenu.nameTranslation.minecraftMinecraft +modmenu.descriptionTranslation.minecraftБазавая гульня. +modmenu.nameTranslation.javaJava +modmenu.descriptionTranslation.javaБягучае асяроддзе Java. +modmenu.javaDistributionNameБягучая: %s +modmenu.optionsНалады Mod Menu +option.modmenu.sortingСартыроўка +option.modmenu.sorting.ascendingA-Z +option.modmenu.sorting.descendingZ-A +option.modmenu.show_librariesБібліятэкі +option.modmenu.show_libraries.trueПаказаны +option.modmenu.show_libraries.falseСхаваны +option.modmenu.hide_config_buttonsКнопка налад +option.modmenu.hide_config_buttons.trueСхавана +option.modmenu.hide_config_buttons.falseПаказана +option.modmenu.hide_badgesІконкі модаў +option.modmenu.hide_badges.trueСхаваны +option.modmenu.hide_badges.falseПаказаны +option.modmenu.compact_listСпіс +option.modmenu.compact_list.trueКампактны +option.modmenu.compact_list.falseСтандартны +option.modmenu.hide_mod_linksСпасылкі модаў +option.modmenu.hide_mod_links.trueСхаваны +option.modmenu.hide_mod_links.falseПаказаны +option.modmenu.hide_mod_creditsАЎтары модаў +option.modmenu.hide_mod_credits.trueСхаваны +option.modmenu.hide_mod_credits.falseПаказаны +option.modmenu.hide_mod_licenseЛіцэнзія модаў +option.modmenu.hide_mod_license.trueСхавана +option.modmenu.hide_mod_license.falseПаказана +option.modmenu.count_childrenДзіцячыя моды +option.modmenu.count_children.trueУлічваюцца +option.modmenu.count_children.falseНе улічваюцца +option.modmenu.count_librariesБібліятэкі +option.modmenu.count_libraries.trueУлічваюцца +option.modmenu.count_libraries.falseНе ўлічваюцца +option.modmenu.count_hidden_modsСхаваныя моды +option.modmenu.count_hidden_mods.trueУлічваюцца +option.modmenu.count_hidden_mods.falseНе ўлічваюцца +option.modmenu.mod_count_locationЛічба модаў +option.modmenu.mod_count_location.title_screenВугал экрана +option.modmenu.mod_count_location.mods_buttonКнопка модаў +option.modmenu.mod_count_location.title_screen_and_mods_buttonАбодва +option.modmenu.mod_count_location.noneНіяк +option.modmenu.easter_eggsСхованкі +option.modmenu.easter_eggs.trueУключаны +option.modmenu.easter_eggs.falseАдключаны +option.modmenu.mods_button_styleКнопка модаў +option.modmenu.mods_button_style.classicНіжэй Realms +option.modmenu.mods_button_style.replace_realmsЗамест Realms +option.modmenu.mods_button_style.shrinkПобач +option.modmenu.mods_button_style.iconІконкі +option.modmenu.random_java_colorsJava колер +option.modmenu.random_java_colors.trueАд распрацоўшчыка +option.modmenu.random_java_colors.falseЗаўсёды чырвоны +option.modmenu.translate_namesНазвы +option.modmenu.translate_names.trueЛакалізаваны +option.modmenu.translate_names.falseНе лакалізаваны +option.modmenu.translate_descriptionsАпісання +option.modmenu.translate_descriptions.trueЛакалізаваныя +option.modmenu.translate_descriptions.falseНе лакалізаваныя +option.modmenu.update_checkerПраверка абнаўленняў +option.modmenu.update_checker.trueУключана +option.modmenu.update_checker.falseАдключана +option.modmenu.button_update_badgeІндыкатар абнаўленняў +option.modmenu.button_update_badge.trueПаказаны +option.modmenu.button_update_badge.falseСхаваны From f6201bb4ed68ce4e15849cdd9a77b15c349921ee Mon Sep 17 00:00:00 2001 From: Alex Gazmanovich <128359229+Gazmanovich@users.noreply.github.com> Date: Wed, 6 Mar 2024 22:27:16 +0300 Subject: [PATCH 05/23] Update be_by.json (#696) --- src/main/resources/assets/modmenu/lang/be_BY.lang | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/assets/modmenu/lang/be_BY.lang b/src/main/resources/assets/modmenu/lang/be_BY.lang index bc0cf954..c51ce954 100644 --- a/src/main/resources/assets/modmenu/lang/be_BY.lang +++ b/src/main/resources/assets/modmenu/lang/be_BY.lang @@ -50,7 +50,7 @@ modmenu.creditsАўтары: modmenu.viewCreditsПаглядзець аўтараў modmenu.licenseЛіцэнзія: modmenu.linksСпасылкі: -modmenu.sourceКрыінца +modmenu.sourceКрыніца modmenu.hasUpdateДаступна абнаўленне: modmenu.childHasUpdateАбнаўленне дзіцячага мода даступна. modmenu.updateTextv%s на %s @@ -72,7 +72,7 @@ modmenu.patreonPatreon modmenu.paypalPayPal modmenu.redditReddit modmenu.twitchTwitch -modmenu.twitterTwitter +modmenu.twitterX (былы Twitter) modmenu.wikiWiki modmenu.youtubeYouTube modmenu.modsFolderАдкрыць папку модаў @@ -121,7 +121,7 @@ option.modmenu.mod_count_location.title_screenВугал экрана option.modmenu.mod_count_location.mods_buttonКнопка модаў option.modmenu.mod_count_location.title_screen_and_mods_buttonАбодва option.modmenu.mod_count_location.noneНіяк -option.modmenu.easter_eggsСхованкі +option.modmenu.easter_eggsСакрэты option.modmenu.easter_eggs.trueУключаны option.modmenu.easter_eggs.falseАдключаны option.modmenu.mods_button_styleКнопка модаў From 0ec9f0c2144dc6bc06695e47113dd1b8bb849939 Mon Sep 17 00:00:00 2001 From: Prospector Date: Fri, 8 Mar 2024 12:09:08 -0800 Subject: [PATCH 06/23] Update twitter name - Change Twitter name to X (Twitter) --- src/main/resources/assets/modmenu/lang/en_US.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/assets/modmenu/lang/en_US.lang b/src/main/resources/assets/modmenu/lang/en_US.lang index 75f4bf42..337b0f94 100644 --- a/src/main/resources/assets/modmenu/lang/en_US.lang +++ b/src/main/resources/assets/modmenu/lang/en_US.lang @@ -74,7 +74,7 @@ modmenu.patreon=Patreon modmenu.paypal=PayPal modmenu.reddit=Reddit modmenu.twitch=Twitch -modmenu.twitter=Twitter +modmenu.twitter=X (Twitter) modmenu.wiki=Wiki modmenu.youtube=YouTube modmenu.modsFolder=Open Mods Folder From ee72f9277981683b12e95d746b9f4feb32f183b6 Mon Sep 17 00:00:00 2001 From: Prospector Date: Fri, 8 Mar 2024 12:42:18 -0800 Subject: [PATCH 07/23] Delay handling API implementations until it's first needed - Delay loading API implementations until it's first needed in attempt to fix Architectury API's usage (beta) --- .../com/terraformersmc/modmenu/ModMenu.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/terraformersmc/modmenu/ModMenu.java b/src/main/java/com/terraformersmc/modmenu/ModMenu.java index 3b889726..19a1f7a7 100644 --- a/src/main/java/com/terraformersmc/modmenu/ModMenu.java +++ b/src/main/java/com/terraformersmc/modmenu/ModMenu.java @@ -40,17 +40,19 @@ public class ModMenu implements ClientModInitializer { public static final Map ROOT_MODS = new HashMap<>(); public static final LinkedListMultimap PARENT_MAP = LinkedListMultimap.create(); - private static Map> configScreenFactories = new HashMap<>(); - private static List>> delayedScreenFactoryProviders = new ArrayList<>(); + private static final Map> configScreenFactories = new HashMap<>(); + private static final List apiImplementations = new ArrayList<>(); private static int cachedDisplayedModCount = -1; public static boolean runningQuilt = FabricLoader.getInstance().isModLoaded("quilt_loader"); public static boolean devEnvironment = FabricLoader.getInstance().isDevelopmentEnvironment(); public static Screen getConfigScreen(String modid, Screen menuScreen) { - if(!delayedScreenFactoryProviders.isEmpty()) { - delayedScreenFactoryProviders.forEach(map -> map.forEach(configScreenFactories::putIfAbsent)); - delayedScreenFactoryProviders.clear(); + for (ModMenuApi api : apiImplementations) { + Map> factoryProviders = api.getProvidedConfigScreenFactories(); + if (!factoryProviders.isEmpty()) { + factoryProviders.forEach(configScreenFactories::putIfAbsent); + } } if (ModMenuConfig.HIDDEN_CONFIGS.getValue().contains(modid)) { return null; @@ -72,7 +74,7 @@ public void initClient() { try { ModMenuApi api = entrypoint.getEntrypoint(); configScreenFactories.put(modId, api.getModConfigScreenFactory()); - delayedScreenFactoryProviders.add(api.getProvidedConfigScreenFactories()); + apiImplementations.add(api); api.attachModpackBadges(modpackMods::add); } catch (Throwable e) { LOGGER.error("Mod {} provides a broken implementation of ModMenuApi", modId, e); @@ -146,9 +148,9 @@ public static String getDisplayedModCount() { if (cachedDisplayedModCount == -1) { // listen, if you have >= 2^32 mods then that's on you cachedDisplayedModCount = Math.toIntExact(MODS.values().stream().filter(mod -> - (ModMenuConfig.COUNT_CHILDREN.getValue() || mod.getParent() == null) && - (ModMenuConfig.COUNT_LIBRARIES.getValue() || !mod.getBadges().contains(Mod.Badge.LIBRARY)) && - (ModMenuConfig.COUNT_HIDDEN_MODS.getValue() || !mod.isHidden()) + (ModMenuConfig.COUNT_CHILDREN.getValue() || mod.getParent() == null) && + (ModMenuConfig.COUNT_LIBRARIES.getValue() || !mod.getBadges().contains(Mod.Badge.LIBRARY)) && + (ModMenuConfig.COUNT_HIDDEN_MODS.getValue() || !mod.isHidden()) ).count()); } return NumberFormat.getInstance().format(cachedDisplayedModCount); From 697ccf7eb327ddc3e5730ff87b484b444b51347b Mon Sep 17 00:00:00 2001 From: Prospector <6166773+Prospector@users.noreply.github.com> Date: Thu, 18 Apr 2024 08:55:10 -0700 Subject: [PATCH 08/23] Add support for custom update checking logic (#708) * Add support for custom update checking logic * Rename ModrinthUtil to UpdateCheckerUtil * Make download link required * Fix search * Use a separate thread for every custom update checker --------- Co-authored-by: Max Henkel --- .../com/terraformersmc/modmenu/ModMenu.java | 12 +- .../modmenu/api/ModMenuApi.java | 10 ++ .../modmenu/api/UpdateChecker.java | 13 ++ .../modmenu/api/UpdateInfo.java | 25 +++ .../gui/widget/DescriptionListWidget.java | 34 +++- .../gui/widget/entries/ModListEntry.java | 2 +- .../modmenu/util/ModrinthUtil.java | 140 --------------- .../modmenu/util/UpdateCheckerThread.java | 17 ++ .../modmenu/util/UpdateCheckerUtil.java | 159 ++++++++++++++++++ .../terraformersmc/modmenu/util/mod/Mod.java | 23 ++- .../modmenu/util/mod/ModSearch.java | 2 +- .../modmenu/util/mod/ModrinthData.java | 49 ------ .../modmenu/util/mod/ModrinthUpdateInfo.java | 39 +++++ .../util/mod/fabric/FabricDummyParentMod.java | 23 ++- .../modmenu/util/mod/fabric/FabricMod.java | 30 +++- .../modmenu/util/mod/quilt/QuiltMod.java | 6 +- .../resources/assets/modmenu/lang/en_US.lang | 1 + 17 files changed, 363 insertions(+), 222 deletions(-) create mode 100644 src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java create mode 100644 src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java delete mode 100644 src/main/java/com/terraformersmc/modmenu/util/ModrinthUtil.java create mode 100644 src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java create mode 100644 src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java delete mode 100644 src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthData.java create mode 100644 src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java diff --git a/src/main/java/com/terraformersmc/modmenu/ModMenu.java b/src/main/java/com/terraformersmc/modmenu/ModMenu.java index 19a1f7a7..53153c5d 100644 --- a/src/main/java/com/terraformersmc/modmenu/ModMenu.java +++ b/src/main/java/com/terraformersmc/modmenu/ModMenu.java @@ -6,13 +6,15 @@ import com.google.gson.GsonBuilder; import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; +import com.terraformersmc.modmenu.api.UpdateChecker; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.config.ModMenuConfig.GameMenuButtonStyle; import com.terraformersmc.modmenu.config.ModMenuConfig.TitleMenuButtonStyle; import com.terraformersmc.modmenu.config.ModMenuConfigManager; import com.terraformersmc.modmenu.event.ModMenuEventHandler; import com.terraformersmc.modmenu.util.GlUtil; -import com.terraformersmc.modmenu.util.ModrinthUtil; +import com.terraformersmc.modmenu.util.TranslationUtil; +import com.terraformersmc.modmenu.util.UpdateCheckerUtil; import com.terraformersmc.modmenu.util.mod.Mod; import com.terraformersmc.modmenu.util.mod.fabric.FabricDummyParentMod; import com.terraformersmc.modmenu.util.mod.fabric.FabricMod; @@ -68,6 +70,7 @@ public static Screen getConfigScreen(String modid, Screen menuScreen) { public void initClient() { ModMenuConfigManager.initializeConfig(); Set modpackMods = new HashSet<>(); + Map updateCheckers = new HashMap<>(); FabricLoader.getInstance().getEntrypointContainers("modmenu", ModMenuApi.class).forEach(entrypoint -> { ModMetadata metadata = entrypoint.getProvider().getMetadata(); String modId = metadata.getId(); @@ -75,6 +78,7 @@ public void initClient() { ModMenuApi api = entrypoint.getEntrypoint(); configScreenFactories.put(modId, api.getModConfigScreenFactory()); apiImplementations.add(api); + updateCheckers.put(modId, api.getUpdateChecker()); api.attachModpackBadges(modpackMods::add); } catch (Throwable e) { LOGGER.error("Mod {} provides a broken implementation of ModMenuApi", modId, e); @@ -91,10 +95,12 @@ public void initClient() { mod = new FabricMod(modContainer, modpackMods); } + mod.setUpdateChecker(updateCheckers.get(mod.getId())); + MODS.put(mod.getId(), mod); } - ModrinthUtil.checkForUpdates(); + UpdateCheckerUtil.checkForUpdates(); Map dummyParents = new HashMap<>(); @@ -136,7 +142,7 @@ public static boolean areModUpdatesAvailable() { continue; } - if (mod.getModrinthData() != null || mod.getChildHasUpdate()) { + if (mod.hasUpdate() || mod.getChildHasUpdate()) { return true; // At least one currently visible mod has an update } } diff --git a/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java b/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java index 7d4434c8..30671387 100644 --- a/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java +++ b/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java @@ -42,6 +42,16 @@ default ConfigScreenFactory getModConfigScreenFactory() { return screen -> null; } + /** + * Used for mods that have their own update checking logic. + * By returning your own {@link UpdateChecker} instance, you can override ModMenus built-in update checking logic. + * + * @return An {@link UpdateChecker} or null if ModMenu should handle update checking. + */ + default UpdateChecker getUpdateChecker() { + return null; + } + /** * Used to provide config screen factories for other mods. This takes second * priority to a mod's own config screen factory provider. For example, if diff --git a/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java b/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java new file mode 100644 index 00000000..1c2aa231 --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java @@ -0,0 +1,13 @@ +package com.terraformersmc.modmenu.api; + +public interface UpdateChecker { + + /** + * Gets called when ModMenu is checking for updates. + * This is done in a separate thread, so this call can/should be blocking. + * + * @return The update info + */ + UpdateInfo checkForUpdates(); + +} diff --git a/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java b/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java new file mode 100644 index 00000000..d28d3721 --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java @@ -0,0 +1,25 @@ +package com.terraformersmc.modmenu.api; + +import org.jetbrains.annotations.Nullable; + +public interface UpdateInfo { + + /** + * @return If an update for the mod is available. + */ + boolean isUpdateAvailable(); + + /** + * @return The message that is getting displayed when an update is available or null to let ModMenu handle displaying the message. + */ + @Nullable + default String getUpdateMessage() { + return null; + } + + /** + * @return The URL to the mod download. + */ + String getDownloadLink(); + +} diff --git a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java index 051f8b13..7b3982d0 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java @@ -2,6 +2,7 @@ import com.mojang.blaze3d.platform.GLX; import com.mojang.blaze3d.vertex.BufferBuilder; +import com.terraformersmc.modmenu.api.UpdateInfo; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.gui.ModsScreen; import com.terraformersmc.modmenu.gui.widget.entries.EntryListWidget; @@ -13,6 +14,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screen.ConfirmChatLinkScreen; import net.minecraft.client.gui.screen.CreditsScreen; +import com.terraformersmc.modmenu.util.mod.ModrinthUpdateInfo; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.render.*; import net.minecraft.client.resource.language.I18n; @@ -32,6 +34,7 @@ public class DescriptionListWidget extends EntryListWidget { private static final String HAS_UPDATE_TEXT = I18n.translate("modmenu.hasUpdate"); private static final String EXPERIMENTAL_TEXT = Formatting.GOLD + I18n.translate("modmenu.experimental"); private static final String MODRINTH_TEXT = I18n.translate("modmenu.modrinth"); + private static final String DOWNLOAD_TEXT = "" + Formatting.BLUE + Formatting.UNDERLINE + I18n.translate("modmenu.downloadLink"); private static final String CHILD_HAS_UPDATE_TEXT = I18n.translate("modmenu.childHasUpdate"); private static final String LINKS_TEXT = I18n.translate("modmenu.links"); private static final String SOURCE_TEXT = "" + Formatting.BLUE + Formatting.UNDERLINE + I18n.translate("modmenu.source"); @@ -103,7 +106,8 @@ public void render(int mouseX, int mouseY, float delta) { } if (ModMenuConfig.UPDATE_CHECKER.getValue() && !ModMenuConfig.DISABLE_UPDATE_CHECKER.getValue().contains(mod.getId())) { - if (mod.getModrinthData() != null) { + UpdateInfo updateInfo = mod.getUpdateInfo(); + if (updateInfo != null && updateInfo.isUpdateAvailable()) { this.entries.add(emptyEntry); int index = 0; @@ -119,12 +123,30 @@ public void render(int mouseX, int mouseY, float delta) { this.entries.add(new DescriptionEntry((String) line, 8)); } - String updateText = "" + Formatting.BLUE + Formatting.UNDERLINE + I18n.translate("modmenu.updateText", VersionUtil.stripPrefix(mod.getModrinthData().versionNumber()), MODRINTH_TEXT); + if (updateInfo instanceof ModrinthUpdateInfo) { + ModrinthUpdateInfo modrinthUpdateInfo = (ModrinthUpdateInfo) updateInfo; + String updateText = "" + Formatting.BLUE + Formatting.UNDERLINE + I18n.translate("modmenu.updateText", VersionUtil.stripPrefix(modrinthUpdateInfo.getVersionNumber()), MODRINTH_TEXT); - String versionLink = String.format("https://modrinth.com/project/%s/version/%s", mod.getModrinthData().projectId(), mod.getModrinthData().versionId()); - - for (Object line : textRenderer.split(updateText, wrapWidth - 16)) { - this.entries.add(new LinkEntry((String) line, versionLink, 8)); + for (Object line : textRenderer.split(updateText, wrapWidth - 16)) { + this.entries.add(new LinkEntry((String) line, modrinthUpdateInfo.getDownloadLink(), 8)); + } + } else { + String updateMessage = updateInfo.getUpdateMessage(); + String downloadLink = updateInfo.getDownloadLink(); + if (updateMessage == null) { + updateMessage = DOWNLOAD_TEXT; + } else { + if (downloadLink != null) { + updateMessage = "" + Formatting.BLUE + Formatting.UNDERLINE + updateMessage; + } + } + for (Object line : textRenderer.split(updateMessage, wrapWidth - 16)) { + if (downloadLink != null) { + this.entries.add(new LinkEntry((String) line, downloadLink, 8)); + } else { + this.entries.add(new DescriptionEntry((String) line, 8)); + } + } } } if (mod.getChildHasUpdate()) { diff --git a/src/main/java/com/terraformersmc/modmenu/gui/widget/entries/ModListEntry.java b/src/main/java/com/terraformersmc/modmenu/gui/widget/entries/ModListEntry.java index 386bd451..14ce17d6 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/widget/entries/ModListEntry.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/widget/entries/ModListEntry.java @@ -63,7 +63,7 @@ public void render(int index, int x, int y, int rowWidth, int rowHeight, BufferB } font.draw(trimmedName, x + iconSize + 3, y + 1, 0xFFFFFF); int updateBadgeXOffset = 0; - if (ModMenuConfig.UPDATE_CHECKER.getValue() && !ModMenuConfig.DISABLE_UPDATE_CHECKER.getValue().contains(modId) && (mod.getModrinthData() != null || mod.getChildHasUpdate())) { + if (ModMenuConfig.UPDATE_CHECKER.getValue() && !ModMenuConfig.DISABLE_UPDATE_CHECKER.getValue().contains(modId) && (mod.hasUpdate() || mod.getChildHasUpdate())) { UpdateAvailableBadge.renderBadge(x + iconSize + 3 + font.getWidth(name) + 2, y); updateBadgeXOffset = 11; } diff --git a/src/main/java/com/terraformersmc/modmenu/util/ModrinthUtil.java b/src/main/java/com/terraformersmc/modmenu/util/ModrinthUtil.java deleted file mode 100644 index 3456b558..00000000 --- a/src/main/java/com/terraformersmc/modmenu/util/ModrinthUtil.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.terraformersmc.modmenu.util; - -import java.io.IOException; -import java.net.URI; -import java.util.*; -import java.util.concurrent.CompletableFuture; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.annotations.SerializedName; -import com.terraformersmc.modmenu.ModMenu; -import com.terraformersmc.modmenu.config.ModMenuConfig; -import com.terraformersmc.modmenu.util.mod.Mod; -import com.terraformersmc.modmenu.util.mod.ModrinthData; -import net.fabricmc.loader.api.FabricLoader; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.methods.RequestBuilder; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.util.EntityUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ModrinthUtil { - public static final Logger LOGGER = LogManager.getLogger("Mod Menu/Update Checker"); - - private static final HttpClient client = HttpClientBuilder.create().build(); - private static boolean apiV2Deprecated = false; - - private static boolean allowsUpdateChecks(Mod mod) { - return mod.allowsUpdateChecks(); - } - - public static void checkForUpdates() { - if (!ModMenuConfig.UPDATE_CHECKER.getValue()) { - return; - } - - CompletableFuture.runAsync(() -> { - LOGGER.info("Checking mod updates..."); - - Map> modHashes = new HashMap<>(); - new ArrayList<>(ModMenu.MODS.values()).stream().filter(ModrinthUtil::allowsUpdateChecks).forEach(mod -> { - String modId = mod.getId(); - - try { - String hash = mod.getSha512Hash(); - - if (hash != null) { - LOGGER.debug("Hash for {} is {}", modId, hash); - modHashes.putIfAbsent(hash, new HashSet<>()); - modHashes.get(hash).add(mod); - } - } catch (IOException e) { - LOGGER.error("Error getting mod hash for mod {}: ", modId, e); - } - }); - - String environment = ModMenu.devEnvironment ? "/development" : ""; - String primaryLoader = ModMenu.runningQuilt ? "quilt" : "fabric"; - List loaders = ModMenu.runningQuilt ? Arrays.asList("fabric", "quilt") : Collections.singletonList("fabric"); - - String mcVer = FabricLoader.getInstance().getModContainer("minecraft").get() - .getMetadata().getVersion().getFriendlyString(); - String[] splitVersion = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID) - .get().getMetadata().getVersion().getFriendlyString().split("\\+", 1); // Strip build metadata for privacy - final String modMenuVersion = splitVersion.length > 1 ? splitVersion[1] : splitVersion[0]; - final String userAgent = String.format("%s/%s (%s/%s%s)", ModMenu.GITHUB_REF, modMenuVersion, mcVer, primaryLoader, environment); - String body = ModMenu.GSON_MINIFIED.toJson(new LatestVersionsFromHashesBody(modHashes.keySet(), loaders, mcVer)); - LOGGER.debug("User agent: " + userAgent); - LOGGER.debug("Body: " + body); - - try { - HttpUriRequest latestVersionsRequest = RequestBuilder.post() - .setEntity(new StringEntity(body)) - .addHeader("User-Agent", userAgent) - .addHeader("Content-Type", "application/json") - .setUri(URI.create("https://api.modrinth.com/v2/version_files/update")) - .build(); - - HttpResponse latestVersionsResponse = client.execute(latestVersionsRequest); - - int status = latestVersionsResponse.getStatusLine().getStatusCode(); - LOGGER.debug("Status: " + status); - if (status == 410) { - apiV2Deprecated = true; - LOGGER.warn("Modrinth API v2 is deprecated, unable to check for mod updates."); - } else if (status == 200) { - JsonObject responseObject = new JsonParser().parse(EntityUtils.toString(latestVersionsResponse.getEntity())).getAsJsonObject(); - LOGGER.debug(String.valueOf(responseObject)); - responseObject.entrySet().forEach(entry -> { - String lookupHash = entry.getKey(); - JsonObject versionObj = entry.getValue().getAsJsonObject(); - String projectId = versionObj.get("project_id").getAsString(); - String versionNumber = versionObj.get("version_number").getAsString(); - String versionId = versionObj.get("id").getAsString(); - List files = new ArrayList<>(); - versionObj.get("files").getAsJsonArray().forEach(files::add); - Optional primaryFile = files.stream() - .filter(file -> file.getAsJsonObject().get("primary").getAsBoolean()).findFirst(); - - if (!primaryFile.isPresent()) { - return; - } - - String versionHash = primaryFile.get().getAsJsonObject().get("hashes").getAsJsonObject().get("sha512").getAsString(); - - if (!Objects.equals(versionHash, lookupHash)) { - // hashes different, there's an update. - modHashes.get(lookupHash).forEach(mod -> { - LOGGER.info("Update available for '{}@{}', (-> {})", mod.getId(), mod.getVersion(), versionNumber); - mod.setModrinthData(new ModrinthData(projectId, versionId, versionNumber)); - }); - } - }); - } - } catch (IOException e) { - LOGGER.error("Error checking for updates: ", e); - } - }); - } - - public static class LatestVersionsFromHashesBody { - public Collection hashes; - public String algorithm = "sha512"; - public Collection loaders; - @SerializedName("game_versions") - public Collection gameVersions; - - public LatestVersionsFromHashesBody(Collection hashes, Collection loaders, String mcVersion) { - this.hashes = hashes; - this.loaders = loaders; - this.gameVersions = new HashSet<>(); - this.gameVersions.add(mcVersion); - } - } -} diff --git a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java new file mode 100644 index 00000000..e29cb8d6 --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java @@ -0,0 +1,17 @@ +package com.terraformersmc.modmenu.util; + +import com.terraformersmc.modmenu.util.mod.Mod; + +public class UpdateCheckerThread extends Thread { + + protected UpdateCheckerThread(Mod mod, Runnable runnable) { + super(runnable); + setDaemon(true); + setName(String.format("Update Checker/%s", mod.getName())); + } + + public static void run(Mod mod, Runnable runnable) { + new UpdateCheckerThread(mod, runnable).start(); + } + +} diff --git a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java new file mode 100644 index 00000000..192034db --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java @@ -0,0 +1,159 @@ +package com.terraformersmc.modmenu.util; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.annotations.SerializedName; +import com.terraformersmc.modmenu.ModMenu; +import com.terraformersmc.modmenu.api.UpdateChecker; +import com.terraformersmc.modmenu.config.ModMenuConfig; +import com.terraformersmc.modmenu.util.mod.Mod; +import com.terraformersmc.modmenu.util.mod.ModrinthUpdateInfo; +import net.fabricmc.loader.api.FabricLoader; + +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.net.URI; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public class UpdateCheckerUtil { + public static final Logger LOGGER = LogManager.getLogger("Mod Menu/Update Checker"); + + private static final HttpClient client = HttpClientBuilder.create().build(); + private static boolean modrinthApiV2Deprecated = false; + + private static boolean allowsUpdateChecks(Mod mod) { + return mod.allowsUpdateChecks(); + } + + public static void checkForUpdates() { + if (!ModMenuConfig.UPDATE_CHECKER.getValue()) { + return; + } + + LOGGER.info("Checking mod updates..."); + + CompletableFuture.runAsync(UpdateCheckerUtil::checkForModrinthUpdates); + checkForCustomUpdates(); + } + + public static void checkForCustomUpdates() { + ModMenu.MODS.values().stream().filter(UpdateCheckerUtil::allowsUpdateChecks).forEach(mod -> { + UpdateChecker updateChecker = mod.getUpdateChecker(); + if (updateChecker == null) { + return; + } + UpdateCheckerThread.run(mod, () -> mod.setUpdateInfo(updateChecker.checkForUpdates())); + }); + } + + public static void checkForModrinthUpdates() { + if (modrinthApiV2Deprecated) { + return; + } + + Map> modHashes = new HashMap<>(); + new ArrayList<>(ModMenu.MODS.values()).stream().filter(UpdateCheckerUtil::allowsUpdateChecks).filter(mod -> mod.getUpdateChecker() == null).forEach(mod -> { + String modId = mod.getId(); + + try { + String hash = mod.getSha512Hash(); + + if (hash != null) { + LOGGER.debug("Hash for {} is {}", modId, hash); + modHashes.putIfAbsent(hash, new HashSet<>()); + modHashes.get(hash).add(mod); + } + } catch (IOException e) { + LOGGER.error("Error getting mod hash for mod {}: ", modId, e); + } + }); + + String environment = ModMenu.devEnvironment ? "/development" : ""; + String primaryLoader = ModMenu.runningQuilt ? "quilt" : "fabric"; + List loaders = ModMenu.runningQuilt ? Arrays.asList("fabric", "quilt") : Arrays.asList("fabric"); + + String mcVer = FabricLoader.getInstance().getModContainer("minecraft").get() + .getMetadata().getVersion().getFriendlyString(); + String[] splitVersion = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID) + .get().getMetadata().getVersion().getFriendlyString().split("\\+", 1); // Strip build metadata for privacy + final String modMenuVersion = splitVersion.length > 1 ? splitVersion[1] : splitVersion[0]; + final String userAgent = String.format("%s/%s (%s/%s%s)", ModMenu.GITHUB_REF, modMenuVersion, mcVer, primaryLoader, environment); + String body = ModMenu.GSON_MINIFIED.toJson(new LatestVersionsFromHashesBody(modHashes.keySet(), loaders, mcVer)); + LOGGER.debug("User agent: " + userAgent); + LOGGER.debug("Body: " + body); + + try { + HttpUriRequest latestVersionsRequest = RequestBuilder.post() + .setEntity(new StringEntity(body)) + .addHeader("User-Agent", userAgent) + .addHeader("Content-Type", "application/json") + .setUri(URI.create("https://api.modrinth.com/v2/version_files/update")) + .build(); + + HttpResponse latestVersionsResponse = client.execute(latestVersionsRequest); + + int status = latestVersionsResponse.getStatusLine().getStatusCode(); + LOGGER.debug("Status: " + status); + if (status == 410) { + modrinthApiV2Deprecated = true; + LOGGER.warn("Modrinth API v2 is deprecated, unable to check for mod updates."); + } else if (status == 200) { + JsonObject responseObject = new JsonParser().parse(EntityUtils.toString(latestVersionsResponse.getEntity())).getAsJsonObject(); + LOGGER.debug(String.valueOf(responseObject)); + responseObject.entrySet().forEach(entry -> { + String lookupHash = entry.getKey(); + JsonObject versionObj = entry.getValue().getAsJsonObject(); + String projectId = versionObj.get("project_id").getAsString(); + String versionNumber = versionObj.get("version_number").getAsString(); + String versionId = versionObj.get("id").getAsString(); + List files = new ArrayList<>(); + versionObj.get("files").getAsJsonArray().forEach(files::add); + Optional primaryFile = files.stream() + .filter(file -> file.getAsJsonObject().get("primary").getAsBoolean()).findFirst(); + + if (!primaryFile.isPresent()) { + return; + } + + String versionHash = primaryFile.get().getAsJsonObject().get("hashes").getAsJsonObject().get("sha512").getAsString(); + + if (!Objects.equals(versionHash, lookupHash)) { + // hashes different, there's an update. + modHashes.get(lookupHash).forEach(mod -> { + LOGGER.info("Update available for '{}@{}', (-> {})", mod.getId(), mod.getVersion(), versionNumber); + mod.setUpdateInfo(new ModrinthUpdateInfo(projectId, versionId, versionNumber)); + }); + } + }); + } + } catch (IOException e) { + LOGGER.error("Error checking for updates: ", e); + } + } + + public static class LatestVersionsFromHashesBody { + public Collection hashes; + public String algorithm = "sha512"; + public Collection loaders; + @SerializedName("game_versions") + public Collection gameVersions; + + public LatestVersionsFromHashesBody(Collection hashes, Collection loaders, String mcVersion) { + this.hashes = hashes; + this.loaders = loaders; + this.gameVersions = new HashSet<>(); + this.gameVersions.add(mcVersion); + } + } +} diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java index 057b719f..4a977f94 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java @@ -1,5 +1,7 @@ package com.terraformersmc.modmenu.util.mod; +import com.terraformersmc.modmenu.api.UpdateChecker; +import com.terraformersmc.modmenu.api.UpdateInfo; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.util.TranslationUtil; import com.terraformersmc.modmenu.util.mod.fabric.FabricIconHandler; @@ -97,17 +99,30 @@ default String getTranslatedDescription() { boolean isReal(); + boolean allowsUpdateChecks(); + @Nullable - ModrinthData getModrinthData(); + UpdateChecker getUpdateChecker(); - boolean allowsUpdateChecks(); + void setUpdateChecker(@Nullable UpdateChecker updateChecker); + + @Nullable + UpdateInfo getUpdateInfo(); + + void setUpdateInfo(@Nullable UpdateInfo updateInfo); + + default boolean hasUpdate() { + UpdateInfo updateInfo = getUpdateInfo(); + if (updateInfo == null) { + return false; + } + return updateInfo.isUpdateAvailable(); + } default @Nullable String getSha512Hash() throws IOException { return null; } - void setModrinthData(ModrinthData modrinthData); - void setChildHasUpdate(); boolean getChildHasUpdate(); diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java b/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java index 04dc5724..584f664d 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java @@ -68,7 +68,7 @@ private static int passesFilters(ModsScreen screen, Mod mod, String query) { || deprecated.contains(query) && mod.getBadges().contains(Mod.Badge.DEPRECATED) // Search for deprecated mods || clientside.contains(query) && mod.getBadges().contains(Mod.Badge.CLIENT) // Search for clientside mods || configurable.contains(query) && screen.getModHasConfigScreen().get(modId) // Search for mods that can be configured - || hasUpdate.contains(query) && mod.getModrinthData() != null // Search for mods that have updates + || hasUpdate.contains(query) && mod.hasUpdate() // Search for mods that have updates ) { return 1; } diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthData.java b/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthData.java deleted file mode 100644 index 181f58e9..00000000 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthData.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.terraformersmc.modmenu.util.mod; - -import java.util.Objects; - -public class ModrinthData { - private final String projectId; - private final String versionId; - private final String versionNumber; - - public ModrinthData(String projectId, String versionId, String versionNumber) { - this.projectId = projectId; - this.versionId = versionId; - this.versionNumber = versionNumber; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ModrinthData)) { - return false; - } - ModrinthData other = (ModrinthData)o; - return Objects.equals(projectId, other.projectId) && Objects.equals(versionId, other.versionId) && Objects.equals(versionNumber, other.versionNumber); - } - - @Override - public int hashCode() { - return Objects.hash(projectId, versionId, versionNumber); - } - - @Override - public String toString() { - return String.format("ModrinthData[projectId: %s, versionId: %s, versionNumber: %s]", projectId, versionId, versionNumber); - } - - public String projectId() { - return projectId; - } - - public String versionId() { - return versionId; - } - - public String versionNumber() { - return versionNumber; - } -} diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java b/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java new file mode 100644 index 00000000..7c122d04 --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java @@ -0,0 +1,39 @@ +package com.terraformersmc.modmenu.util.mod; + +import com.terraformersmc.modmenu.api.UpdateInfo; + +public class ModrinthUpdateInfo implements UpdateInfo { + + protected String projectId; + protected String versionId; + protected String versionNumber; + + public ModrinthUpdateInfo(String projectId, String versionId, String versionNumber) { + this.projectId = projectId; + this.versionId = versionId; + this.versionNumber = versionNumber; + } + + @Override + public boolean isUpdateAvailable() { + return true; + } + + @Override + public String getDownloadLink() { + return String.format("https://modrinth.com/project/%s/version/%s", projectId, versionId); + } + + public String getProjectId() { + return projectId; + } + + public String getVersionId() { + return versionId; + } + + public String getVersionNumber() { + return versionNumber; + } + +} diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java index 85272f47..a624ce0d 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java @@ -1,9 +1,10 @@ package com.terraformersmc.modmenu.util.mod.fabric; import com.terraformersmc.modmenu.ModMenu; +import com.terraformersmc.modmenu.api.UpdateChecker; +import com.terraformersmc.modmenu.api.UpdateInfo; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.util.mod.Mod; -import com.terraformersmc.modmenu.util.mod.ModrinthData; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import org.jetbrains.annotations.NotNull; @@ -157,18 +158,28 @@ public boolean isReal() { } @Override - public @Nullable ModrinthData getModrinthData() { + public boolean allowsUpdateChecks() { + return false; + } + + @Override + public @Nullable UpdateChecker getUpdateChecker() { return null; } @Override - public void setModrinthData(ModrinthData modrinthData) { - // Not a real mod, won't exist on Modrinth + public void setUpdateChecker(@Nullable UpdateChecker updateChecker) { + } @Override - public boolean allowsUpdateChecks() { - return false; + public @Nullable UpdateInfo getUpdateInfo() { + return null; + } + + @Override + public void setUpdateInfo(@Nullable UpdateInfo updateInfo) { + } @Override diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java index 6dfe487f..bb6b8875 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java @@ -5,11 +5,12 @@ import com.google.common.hash.Hashing; import com.google.common.io.Files; import com.terraformersmc.modmenu.ModMenu; +import com.terraformersmc.modmenu.api.UpdateChecker; +import com.terraformersmc.modmenu.api.UpdateInfo; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.util.OptionalUtil; import com.terraformersmc.modmenu.util.VersionUtil; import com.terraformersmc.modmenu.util.mod.Mod; -import com.terraformersmc.modmenu.util.mod.ModrinthData; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.metadata.*; @@ -39,7 +40,8 @@ public class FabricMod implements Mod { protected final Map links = new HashMap<>(); - protected @Nullable ModrinthData modrinthData = null; + protected @Nullable UpdateChecker updateChecker = null; + protected @Nullable UpdateInfo updateInfo = null; protected boolean defaultIconWarning = true; @@ -275,20 +277,30 @@ public boolean isReal() { } @Override - public @Nullable ModrinthData getModrinthData() { - return this.modrinthData; + public boolean allowsUpdateChecks() { + return this.allowsUpdateChecks || ModMenuConfig.DISABLE_UPDATE_CHECKER.getValue().contains(this.getId()); } @Override - public boolean allowsUpdateChecks() { - return this.allowsUpdateChecks || ModMenuConfig.DISABLE_UPDATE_CHECKER.getValue().contains(this.getId()); + public @Nullable UpdateChecker getUpdateChecker() { + return updateChecker; + } + + @Override + public void setUpdateChecker(@Nullable UpdateChecker updateChecker) { + this.updateChecker = updateChecker; + } + + @Override + public @Nullable UpdateInfo getUpdateInfo() { + return updateInfo; } @Override - public void setModrinthData(ModrinthData modrinthData) { - this.modrinthData = modrinthData; + public void setUpdateInfo(@Nullable UpdateInfo updateInfo) { + this.updateInfo = updateInfo; String parent = getParent(); - if (parent != null && modrinthData != null) { + if (parent != null && updateInfo != null && updateInfo.isUpdateAvailable()) { ModMenu.MODS.get(parent).setChildHasUpdate(); } } diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java index 8d335fc1..5925ea8a 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java @@ -3,7 +3,7 @@ import com.google.common.collect.Lists; import com.google.common.hash.Hashing; import com.google.common.io.Files; -import com.terraformersmc.modmenu.util.ModrinthUtil; +import com.terraformersmc.modmenu.util.UpdateCheckerUtil; import com.terraformersmc.modmenu.util.mod.fabric.FabricMod; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -69,7 +69,7 @@ public QuiltMod(net.fabricmc.loader.api.ModContainer fabricModContainer, Set paths : container.getSourcePaths()) { List jars = paths.stream().filter(p -> p.toString().toLowerCase(Locale.ROOT).endsWith(".jar")).collect(Collectors.toList()); @@ -78,7 +78,7 @@ public QuiltMod(net.fabricmc.loader.api.ModContainer fabricModContainer, Set Date: Thu, 18 Apr 2024 17:56:24 +0200 Subject: [PATCH 09/23] Fix disable update checks config not working for individual mods (#703) - Fixed disable_update_checker config option not working --- .../terraformersmc/modmenu/util/mod/fabric/FabricMod.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java index bb6b8875..d3aec99c 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java @@ -278,7 +278,11 @@ public boolean isReal() { @Override public boolean allowsUpdateChecks() { - return this.allowsUpdateChecks || ModMenuConfig.DISABLE_UPDATE_CHECKER.getValue().contains(this.getId()); + if (ModMenuConfig.DISABLE_UPDATE_CHECKER.getValue().contains(this.getId())) { + return false; + } + + return this.allowsUpdateChecks; } @Override From 7e9cd22c43aa5892c06ab44cec7e154510386ad0 Mon Sep 17 00:00:00 2001 From: Lilly Rose Berner Date: Mon, 22 Apr 2024 21:48:10 +0200 Subject: [PATCH 10/23] Add option to choose which update channel to receive notifications for (#704) * Allow choosing an update channel to receive notifications for * Pass user preferred update channel to UpdateChecker * Change method of getting the preferred update channel * Request preferred version types from Modrinth only * Switch update channel and quick configure button order * Rerun update checks when clicking done in the modmenu options screen - No longer promotes updates to beta or alpha versions by default --- .../com/terraformersmc/modmenu/ModMenu.java | 6 ++- .../modmenu/api/UpdateChannel.java | 19 ++++++++++ .../modmenu/api/UpdateChecker.java | 4 +- .../modmenu/api/UpdateInfo.java | 5 ++- .../modmenu/config/ModMenuConfig.java | 2 + .../modmenu/gui/ModMenuOptionsScreen.java | 2 + .../modmenu/util/UpdateCheckerUtil.java | 38 +++++++++++++++++-- .../terraformersmc/modmenu/util/mod/Mod.java | 3 +- .../modmenu/util/mod/ModrinthUpdateInfo.java | 16 +++++--- .../resources/assets/modmenu/lang/en_US.lang | 4 ++ 10 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/terraformersmc/modmenu/api/UpdateChannel.java diff --git a/src/main/java/com/terraformersmc/modmenu/ModMenu.java b/src/main/java/com/terraformersmc/modmenu/ModMenu.java index 53153c5d..f41af9b9 100644 --- a/src/main/java/com/terraformersmc/modmenu/ModMenu.java +++ b/src/main/java/com/terraformersmc/modmenu/ModMenu.java @@ -100,7 +100,7 @@ public void initClient() { MODS.put(mod.getId(), mod); } - UpdateCheckerUtil.checkForUpdates(); + checkForUpdates(); Map dummyParents = new HashMap<>(); @@ -128,6 +128,10 @@ public static void clearModCountCache() { cachedDisplayedModCount = -1; } + public static void checkForUpdates() { + UpdateCheckerUtil.checkForUpdates(); + } + public static boolean areModUpdatesAvailable() { if (!ModMenuConfig.UPDATE_CHECKER.getValue()) { return false; diff --git a/src/main/java/com/terraformersmc/modmenu/api/UpdateChannel.java b/src/main/java/com/terraformersmc/modmenu/api/UpdateChannel.java new file mode 100644 index 00000000..4c51835c --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/api/UpdateChannel.java @@ -0,0 +1,19 @@ +package com.terraformersmc.modmenu.api; + +import com.terraformersmc.modmenu.config.ModMenuConfig; + +/** + * Supported update channels, in ascending order by stability. + */ +public enum UpdateChannel { + ALPHA, + BETA, + RELEASE; + + /** + * @return the user's preferred update channel. + */ + public static UpdateChannel getUserPreference() { + return ModMenuConfig.UPDATE_CHANNEL.getValue(); + } +} diff --git a/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java b/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java index 1c2aa231..de80fd93 100644 --- a/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java +++ b/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java @@ -1,13 +1,13 @@ package com.terraformersmc.modmenu.api; public interface UpdateChecker { - /** * Gets called when ModMenu is checking for updates. * This is done in a separate thread, so this call can/should be blocking. * + *

Your update checker should aim to return an update on the same or a more stable channel than the user's preference which you can get via {@link UpdateChannel#getUserPreference()}.

+ * * @return The update info */ UpdateInfo checkForUpdates(); - } diff --git a/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java b/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java index d28d3721..02312ad5 100644 --- a/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java +++ b/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java @@ -3,7 +3,6 @@ import org.jetbrains.annotations.Nullable; public interface UpdateInfo { - /** * @return If an update for the mod is available. */ @@ -22,4 +21,8 @@ default String getUpdateMessage() { */ String getDownloadLink(); + /** + * @return The update channel this update is available for. + */ + UpdateChannel getUpdateChannel(); } diff --git a/src/main/java/com/terraformersmc/modmenu/config/ModMenuConfig.java b/src/main/java/com/terraformersmc/modmenu/config/ModMenuConfig.java index f9bd5e5f..69f8c1e8 100644 --- a/src/main/java/com/terraformersmc/modmenu/config/ModMenuConfig.java +++ b/src/main/java/com/terraformersmc/modmenu/config/ModMenuConfig.java @@ -1,6 +1,7 @@ package com.terraformersmc.modmenu.config; import com.google.gson.annotations.SerializedName; +import com.terraformersmc.modmenu.api.UpdateChannel; import com.terraformersmc.modmenu.config.option.BooleanConfigOption; import com.terraformersmc.modmenu.config.option.ConfigOption; import com.terraformersmc.modmenu.config.option.EnumConfigOption; @@ -41,6 +42,7 @@ public class ModMenuConfig { public static final StringSetConfigOption DISABLE_UPDATE_CHECKER = new StringSetConfigOption("disable_update_checker", new HashSet<>()); public static final BooleanConfigOption UPDATE_CHECKER = new BooleanConfigOption("update_checker", true); public static final BooleanConfigOption BUTTON_UPDATE_BADGE = new BooleanConfigOption("button_update_badge", true); + public static final EnumConfigOption UPDATE_CHANNEL = new EnumConfigOption<>("update_channel", UpdateChannel.RELEASE); public static final BooleanConfigOption QUICK_CONFIGURE = new BooleanConfigOption("quick_configure", true); public static ConfigOption[] asOptions() { diff --git a/src/main/java/com/terraformersmc/modmenu/gui/ModMenuOptionsScreen.java b/src/main/java/com/terraformersmc/modmenu/gui/ModMenuOptionsScreen.java index 8f8f7907..9de7851a 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/ModMenuOptionsScreen.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/ModMenuOptionsScreen.java @@ -1,5 +1,6 @@ package com.terraformersmc.modmenu.gui; +import com.terraformersmc.modmenu.ModMenu; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.config.ModMenuConfigManager; import com.terraformersmc.modmenu.gui.widget.ConfigOptionListWidget; @@ -56,6 +57,7 @@ public void mouseClicked(int mouseX, int mouseY, int button) { public void buttonClicked(ButtonWidget button) { switch (button.id) { case DONE: + ModMenu.checkForUpdates(); ModMenuConfigManager.save(); ModMenuOptionsScreen.this.minecraft.openScreen(ModMenuOptionsScreen.this.previous); break; diff --git a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java index 192034db..ab0f2c0b 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java +++ b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java @@ -5,6 +5,7 @@ import com.google.gson.JsonParser; import com.google.gson.annotations.SerializedName; import com.terraformersmc.modmenu.ModMenu; +import com.terraformersmc.modmenu.api.UpdateChannel; import com.terraformersmc.modmenu.api.UpdateChecker; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.util.mod.Mod; @@ -89,7 +90,20 @@ public static void checkForModrinthUpdates() { .get().getMetadata().getVersion().getFriendlyString().split("\\+", 1); // Strip build metadata for privacy final String modMenuVersion = splitVersion.length > 1 ? splitVersion[1] : splitVersion[0]; final String userAgent = String.format("%s/%s (%s/%s%s)", ModMenu.GITHUB_REF, modMenuVersion, mcVer, primaryLoader, environment); - String body = ModMenu.GSON_MINIFIED.toJson(new LatestVersionsFromHashesBody(modHashes.keySet(), loaders, mcVer)); + + List updateChannels; + UpdateChannel preferredChannel = UpdateChannel.getUserPreference(); + + if (preferredChannel == UpdateChannel.RELEASE) { + updateChannels = Arrays.asList(UpdateChannel.RELEASE); + } else if (preferredChannel == UpdateChannel.BETA) { + updateChannels = Arrays.asList(UpdateChannel.BETA, UpdateChannel.RELEASE); + } else { + updateChannels = Arrays.asList(UpdateChannel.ALPHA, UpdateChannel.BETA, UpdateChannel.RELEASE); + } + + String body = ModMenu.GSON_MINIFIED.toJson(new LatestVersionsFromHashesBody(modHashes.keySet(), loaders, mcVer, updateChannels)); + LOGGER.debug("User agent: " + userAgent); LOGGER.debug("Body: " + body); @@ -115,6 +129,7 @@ public static void checkForModrinthUpdates() { String lookupHash = entry.getKey(); JsonObject versionObj = entry.getValue().getAsJsonObject(); String projectId = versionObj.get("project_id").getAsString(); + String versionType = versionObj.get("version_type").getAsString(); String versionNumber = versionObj.get("version_number").getAsString(); String versionId = versionObj.get("id").getAsString(); List files = new ArrayList<>(); @@ -126,13 +141,14 @@ public static void checkForModrinthUpdates() { return; } + UpdateChannel updateChannel = UpdateCheckerUtil.getUpdateChannel(versionType); String versionHash = primaryFile.get().getAsJsonObject().get("hashes").getAsJsonObject().get("sha512").getAsString(); if (!Objects.equals(versionHash, lookupHash)) { // hashes different, there's an update. modHashes.get(lookupHash).forEach(mod -> { LOGGER.info("Update available for '{}@{}', (-> {})", mod.getId(), mod.getVersion(), versionNumber); - mod.setUpdateInfo(new ModrinthUpdateInfo(projectId, versionId, versionNumber)); + mod.setUpdateInfo(new ModrinthUpdateInfo(projectId, versionId, versionNumber, updateChannel)); }); } }); @@ -142,18 +158,34 @@ public static void checkForModrinthUpdates() { } } + private static UpdateChannel getUpdateChannel(String versionType) { + try { + return UpdateChannel.valueOf(versionType.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException | NullPointerException e) { + return UpdateChannel.RELEASE; + } + } + public static class LatestVersionsFromHashesBody { public Collection hashes; public String algorithm = "sha512"; public Collection loaders; @SerializedName("game_versions") public Collection gameVersions; + @SerializedName("version_types") + public Collection versionTypes; - public LatestVersionsFromHashesBody(Collection hashes, Collection loaders, String mcVersion) { + public LatestVersionsFromHashesBody(Collection hashes, Collection loaders, String mcVersion, Collection updateChannels) { this.hashes = hashes; this.loaders = loaders; this.gameVersions = new HashSet<>(); this.gameVersions.add(mcVersion); + + this.versionTypes = new HashSet<>(); + + for (UpdateChannel updateChannel : updateChannels) { + this.versionTypes.add(updateChannel.toString().toLowerCase()); + } } } } diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java index 4a977f94..69d57e3b 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java @@ -116,7 +116,8 @@ default boolean hasUpdate() { if (updateInfo == null) { return false; } - return updateInfo.isUpdateAvailable(); + + return updateInfo.isUpdateAvailable() && updateInfo.getUpdateChannel().compareTo(ModMenuConfig.UPDATE_CHANNEL.getValue()) >= 0; } default @Nullable String getSha512Hash() throws IOException { diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java b/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java index 7c122d04..254aa4f6 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java @@ -1,17 +1,19 @@ package com.terraformersmc.modmenu.util.mod; +import com.terraformersmc.modmenu.api.UpdateChannel; import com.terraformersmc.modmenu.api.UpdateInfo; public class ModrinthUpdateInfo implements UpdateInfo { + protected final String projectId; + protected final String versionId; + protected final String versionNumber; + protected final UpdateChannel updateChannel; - protected String projectId; - protected String versionId; - protected String versionNumber; - - public ModrinthUpdateInfo(String projectId, String versionId, String versionNumber) { + public ModrinthUpdateInfo(String projectId, String versionId, String versionNumber, UpdateChannel updateChannel) { this.projectId = projectId; this.versionId = versionId; this.versionNumber = versionNumber; + this.updateChannel = updateChannel; } @Override @@ -36,4 +38,8 @@ public String getVersionNumber() { return versionNumber; } + @Override + public UpdateChannel getUpdateChannel() { + return this.updateChannel; + } } diff --git a/src/main/resources/assets/modmenu/lang/en_US.lang b/src/main/resources/assets/modmenu/lang/en_US.lang index f02a7524..875813dc 100644 --- a/src/main/resources/assets/modmenu/lang/en_US.lang +++ b/src/main/resources/assets/modmenu/lang/en_US.lang @@ -156,3 +156,7 @@ option.modmenu.button_update_badge.false=Hidden option.modmenu.quick_configure=Quick Configure option.modmenu.quick_configure.true=Enabled option.modmenu.quick_configure.false=Disabled +option.modmenu.update_channel=Update Channel +option.modmenu.update_channel.alpha=All +option.modmenu.update_channel.beta=Release & Beta +option.modmenu.update_channel.release=Release From f2f2e91c232bf8698ef1273e2b221aad4bfe8c2f Mon Sep 17 00:00:00 2001 From: Lilly Rose Berner Date: Mon, 22 Apr 2024 21:53:31 +0200 Subject: [PATCH 11/23] Group mod credits by role instead of bunching them together (#706) * Display credits by role instead of bunching them together * Add newlines between credit groups, allow translating role names * Fix minor code style issues * Improve translation key generation and fallback role names * Sort contributors within roles * Fix Fabric authors being credited wrong - Group mod credits together by role --- .../gui/widget/DescriptionListWidget.java | 37 +++++++++++++++++-- .../terraformersmc/modmenu/util/mod/Mod.java | 10 ++++- .../util/mod/fabric/FabricDummyParentMod.java | 9 +++-- .../modmenu/util/mod/fabric/FabricMod.java | 37 +++++++++++++------ .../modmenu/util/mod/quilt/QuiltMod.java | 35 ++++++++++++++---- .../resources/assets/modmenu/lang/en_US.lang | 7 ++++ 6 files changed, 108 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java index 7b3982d0..b74f3296 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java @@ -22,9 +22,12 @@ import net.minecraft.util.math.MathHelper; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; @@ -214,7 +217,8 @@ public void render(int mouseX, int mouseY, float delta) { this.entries.add(new MojangCreditsEntry((String) line)); } } else if (!"java".equals(mod.getId())) { - List credits = mod.getCredits(); + SortedMap> credits = mod.getCredits(); + if (!credits.isEmpty()) { this.entries.add(emptyEntry); @@ -222,12 +226,31 @@ public void render(int mouseX, int mouseY, float delta) { this.entries.add(new DescriptionEntry((String) line)); } - for (String credit : credits) { + Iterator>> iterator = credits.entrySet().iterator(); + + while (iterator.hasNext()) { int indent = 8; - for (Object line : textRenderer.split(credit, wrapWidth - 16)) { + + Map.Entry> role = iterator.next(); + String roleName = role.getKey(); + + for (Object line : textRenderer.split(this.creditsRoleText(roleName), wrapWidth - 16)) { this.entries.add(new DescriptionEntry((String) line, indent)); indent = 16; } + + for (String contributor : role.getValue()) { + indent = 16; + + for (Object line : textRenderer.split(contributor, wrapWidth - 24)) { + this.entries.add(new DescriptionEntry((String) line, indent)); + indent = 24; + } + } + + if (iterator.hasNext()) { + this.entries.add(emptyEntry); + } } } } @@ -413,6 +436,14 @@ public void confirmResult(boolean result, int id) { minecraft.openScreen(this.parent); } + private String creditsRoleText(String roleName) { + // Replace spaces and dashes in role names with underscores if they exist + // Notably Quilted Fabric API does this with FabricMC as "Upstream Owner" + String translationKey = roleName.replaceAll("[\\s-]", "_"); + + return I18n.translate("modmenu.credits.role." + translationKey) + ":"; + } + protected class DescriptionEntry implements EntryListWidget.Entry { protected String text; protected int indent; diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java index 69d57e3b..d5e1d210 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java @@ -70,11 +70,17 @@ default String getTranslatedDescription() { @NotNull List getAuthors(); + /** + * @return a mapping of contributors to their roles. + */ @NotNull - List getContributors(); + Map> getContributors(); + /** + * @return a mapping of roles to each contributor with that role. + */ @NotNull - List getCredits(); + SortedMap> getCredits(); @NotNull Set getBadges(); diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java index a624ce0d..c1cf2229 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java @@ -1,5 +1,6 @@ package com.terraformersmc.modmenu.util.mod.fabric; +import com.google.common.collect.ImmutableMap; import com.terraformersmc.modmenu.ModMenu; import com.terraformersmc.modmenu.api.UpdateChecker; import com.terraformersmc.modmenu.api.UpdateInfo; @@ -89,13 +90,13 @@ public FabricDummyParentMod(FabricMod host, String id) { } @Override - public @NotNull List getContributors() { - return new ArrayList<>(); + public @NotNull Map> getContributors() { + return ImmutableMap.of(); } @Override - public @NotNull List getCredits() { - return new ArrayList<>(); + public @NotNull SortedMap> getCredits() { + return new TreeMap<>(); } @Override diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java index d3aec99c..8ad3caf7 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java @@ -209,20 +209,35 @@ public FabricMod(ModContainer modContainer, Set modpackMods) { } @Override - public @NotNull List getContributors() { - List authors = metadata.getContributors().stream().map(Person::getName).collect(Collectors.toList()); - if ("minecraft".equals(getId()) && authors.isEmpty()) { - return Lists.newArrayList(); + public @NotNull Map> getContributors() { + Map> contributors = new HashMap<>(); + + for (Person contributor : this.metadata.getContributors()) { + contributors.put(contributor.getName(), Arrays.asList("Contributor")); } - return authors; + + return contributors; } - @NotNull - public List getCredits() { - List list = new ArrayList<>(); - list.addAll(getAuthors()); - list.addAll(getContributors()); - return list; + @Override + public @NotNull SortedMap> getCredits() { + SortedMap> credits = new TreeMap<>(); + + List authors = this.getAuthors(); + Map> contributors = this.getContributors(); + + for (String author : authors) { + contributors.put(author, Arrays.asList("Author")); + } + + for (Map.Entry> contributor : contributors.entrySet()) { + for (String role : contributor.getValue()) { + credits.computeIfAbsent(role, key -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); + credits.get(role).add(contributor.getKey()); + } + } + + return credits; } @Override diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java index 5925ea8a..9a967c17 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java @@ -16,9 +16,17 @@ import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.stream.Collectors; public class QuiltMod extends FabricMod { @@ -52,17 +60,30 @@ public QuiltMod(net.fabricmc.loader.api.ModContainer fabricModContainer, Set getContributors() { - List authors = metadata.contributors().stream().map(modContributor -> modContributor.name() + " (" + modContributor.role() + ")").collect(Collectors.toList()); - if ("minecraft".equals(getId()) && authors.isEmpty()) { - return Lists.newArrayList(); + public @NotNull Map> getContributors() { + Map> contributors = new HashMap<>(); + + for (ModContributor contributor : this.metadata.contributors()) { + contributors.put(contributor.name(), contributor.roles()); } - return authors; + + return contributors; } @Override - public @NotNull List getCredits() { - return this.getContributors(); + public @NotNull SortedMap> getCredits() { + SortedMap> credits = new TreeMap<>(); + + Map> contributors = this.getContributors(); + + for (Map.Entry> contributor : contributors.entrySet()) { + for (String role : contributor.getValue()) { + credits.computeIfAbsent(role, key -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); + credits.get(role).add(contributor.getKey()); + } + } + + return credits; } diff --git a/src/main/resources/assets/modmenu/lang/en_US.lang b/src/main/resources/assets/modmenu/lang/en_US.lang index 875813dc..60ccd58b 100644 --- a/src/main/resources/assets/modmenu/lang/en_US.lang +++ b/src/main/resources/assets/modmenu/lang/en_US.lang @@ -78,6 +78,13 @@ modmenu.twitch=Twitch modmenu.twitter=X (Twitter) modmenu.wiki=Wiki modmenu.youtube=YouTube +modmenu.credits.role.author=Authors +modmenu.credits.role.contributor=Contributors +modmenu.credits.role.translator=Translators +modmenu.credits.role.maintainer=Maintainers +modmenu.credits.role.playtester=Playtesters +modmenu.credits.role.illustrator=Illustrators +modmenu.credits.role.owner=Owners modmenu.modsFolder=Open Mods Folder modmenu.configsFolder=Open Configs Folder modmenu.nameTranslation.minecraft=Minecraft From 2d33c87c192747c1a081ee9589c3ea2913f286f7 Mon Sep 17 00:00:00 2001 From: Lilly Rose Berner Date: Mon, 22 Apr 2024 21:54:09 +0200 Subject: [PATCH 12/23] Fix user agent stripping not working (#707) - Fix Mod Menu build metadata not being properly stripped from the User Agent string generation for privacy --- .../com/terraformersmc/modmenu/util/UpdateCheckerUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java index ab0f2c0b..69014a64 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java +++ b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java @@ -86,9 +86,9 @@ public static void checkForModrinthUpdates() { String mcVer = FabricLoader.getInstance().getModContainer("minecraft").get() .getMetadata().getVersion().getFriendlyString(); - String[] splitVersion = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID) - .get().getMetadata().getVersion().getFriendlyString().split("\\+", 1); // Strip build metadata for privacy - final String modMenuVersion = splitVersion.length > 1 ? splitVersion[1] : splitVersion[0]; + String version = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID) + .get().getMetadata().getVersion().getFriendlyString(); + final String modMenuVersion = version.split("\\+", 2)[0]; // Strip build metadata for privacy final String userAgent = String.format("%s/%s (%s/%s%s)", ModMenu.GITHUB_REF, modMenuVersion, mcVer, primaryLoader, environment); List updateChannels; From 10b7e253a72f918089b7733482176512a7184b5f Mon Sep 17 00:00:00 2001 From: Lilly Rose Berner Date: Mon, 22 Apr 2024 22:17:08 +0200 Subject: [PATCH 13/23] Add provided update checkers, loader update checkers (#711) - Add update checkers for Fabric Loader and Quilt Loader - Add API for mods to provide update checkers for other mods --- .../com/terraformersmc/modmenu/ModMenu.java | 10 +- .../modmenu/ModMenuModMenuCompat.java | 14 +- .../modmenu/api/ModMenuApi.java | 10 ++ .../terraformersmc/modmenu/util/HttpUtil.java | 47 +++++ .../terraformersmc/modmenu/util/JsonUtil.java | 39 +++++ .../modmenu/util/UpdateCheckerUtil.java | 33 ++-- .../modmenu/util/VersionUtil.java | 8 +- .../mod/fabric/FabricLoaderUpdateChecker.java | 152 ++++++++++++++++ .../modmenu/util/mod/fabric/FabricMod.java | 2 +- .../mod/quilt/QuiltLoaderUpdateChecker.java | 162 ++++++++++++++++++ 10 files changed, 454 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/terraformersmc/modmenu/util/HttpUtil.java create mode 100644 src/main/java/com/terraformersmc/modmenu/util/JsonUtil.java create mode 100644 src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java create mode 100644 src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java diff --git a/src/main/java/com/terraformersmc/modmenu/ModMenu.java b/src/main/java/com/terraformersmc/modmenu/ModMenu.java index f41af9b9..9c50eb1d 100644 --- a/src/main/java/com/terraformersmc/modmenu/ModMenu.java +++ b/src/main/java/com/terraformersmc/modmenu/ModMenu.java @@ -71,6 +71,8 @@ public void initClient() { ModMenuConfigManager.initializeConfig(); Set modpackMods = new HashSet<>(); Map updateCheckers = new HashMap<>(); + Map providedUpdateCheckers = new HashMap<>(); + FabricLoader.getInstance().getEntrypointContainers("modmenu", ModMenuApi.class).forEach(entrypoint -> { ModMetadata metadata = entrypoint.getProvider().getMetadata(); String modId = metadata.getId(); @@ -79,6 +81,7 @@ public void initClient() { configScreenFactories.put(modId, api.getModConfigScreenFactory()); apiImplementations.add(api); updateCheckers.put(modId, api.getUpdateChecker()); + providedUpdateCheckers.putAll(api.getProvidedUpdateCheckers()); api.attachModpackBadges(modpackMods::add); } catch (Throwable e) { LOGGER.error("Mod {} provides a broken implementation of ModMenuApi", modId, e); @@ -95,9 +98,14 @@ public void initClient() { mod = new FabricMod(modContainer, modpackMods); } - mod.setUpdateChecker(updateCheckers.get(mod.getId())); + UpdateChecker updateChecker = updateCheckers.get(mod.getId()); + + if (updateChecker == null) { + updateChecker = providedUpdateCheckers.get(mod.getId()); + } MODS.put(mod.getId(), mod); + mod.setUpdateChecker(updateChecker); } checkForUpdates(); diff --git a/src/main/java/com/terraformersmc/modmenu/ModMenuModMenuCompat.java b/src/main/java/com/terraformersmc/modmenu/ModMenuModMenuCompat.java index 12e7cc86..577b016b 100644 --- a/src/main/java/com/terraformersmc/modmenu/ModMenuModMenuCompat.java +++ b/src/main/java/com/terraformersmc/modmenu/ModMenuModMenuCompat.java @@ -3,14 +3,17 @@ import com.google.common.collect.ImmutableMap; import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; +import com.terraformersmc.modmenu.api.UpdateChecker; import com.terraformersmc.modmenu.gui.ModMenuOptionsScreen; +import com.terraformersmc.modmenu.util.mod.fabric.FabricLoaderUpdateChecker; +import com.terraformersmc.modmenu.util.mod.quilt.QuiltLoaderUpdateChecker; + import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screen.options.OptionsScreen; import java.util.Map; public class ModMenuModMenuCompat implements ModMenuApi { - @Override public ConfigScreenFactory getModConfigScreenFactory() { return ModMenuOptionsScreen::new; @@ -20,4 +23,13 @@ public ConfigScreenFactory getModConfigScreenFactory() { public Map> getProvidedConfigScreenFactories() { return ImmutableMap.of("minecraft", parent -> new OptionsScreen(parent, Minecraft.getInstance().options)); } + + @Override + public Map getProvidedUpdateCheckers() { + if (ModMenu.runningQuilt) { + return ImmutableMap.of("quilt_loader", new QuiltLoaderUpdateChecker()); + } else { + return ImmutableMap.of("fabricloader", new FabricLoaderUpdateChecker()); + } + } } diff --git a/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java b/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java index 30671387..751d6139 100644 --- a/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java +++ b/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java @@ -68,6 +68,16 @@ default Map> getProvidedConfigScreenFactories() { return ImmutableMap.of(); } + /** + * Used to provide update checkers for other mods. A mod registering its own + * update checker will take priority over any provided ones should both exist. + * + * @return a map of mod ids to update checkers. + */ + default Map getProvidedUpdateCheckers() { + return ImmutableMap.of(); + } + /** * Used to mark mods with a badge indicating that they are * provided by a modpack. diff --git a/src/main/java/com/terraformersmc/modmenu/util/HttpUtil.java b/src/main/java/com/terraformersmc/modmenu/util/HttpUtil.java new file mode 100644 index 00000000..6fa95170 --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/util/HttpUtil.java @@ -0,0 +1,47 @@ +package com.terraformersmc.modmenu.util; + +import java.io.IOException; +import java.util.Optional; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; + +import com.terraformersmc.modmenu.ModMenu; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; + +public class HttpUtil { + private static final String USER_AGENT = buildUserAgent(); + private static final CloseableHttpClient HTTP_CLIENT = HttpClientBuilder.create().build(); + + private HttpUtil() {} + + public static HttpResponse request(RequestBuilder builder) throws IOException { + builder.setHeader("User-Agent", USER_AGENT); + return HTTP_CLIENT.execute(builder.build()); + } + + private static String buildUserAgent() { + String env = ModMenu.devEnvironment ? "/development" : ""; + String loader = ModMenu.runningQuilt ? "quilt" : "fabric"; + + String modMenuVersion = getModMenuVersion(ModMenu.MOD_ID); + String minecraftVersion = getModMenuVersion("minecraft"); + + // -> TerraformersMC/ModMenu/9.1.0 (1.20.3/quilt/development) + return String.format("%s/%s (%s/%s%s)", ModMenu.GITHUB_REF, modMenuVersion, minecraftVersion, loader, env); + } + + private static String getModMenuVersion(String modId) { + Optional container = FabricLoader.getInstance().getModContainer(modId); + + if (!container.isPresent()) { + throw new RuntimeException("Unable to find Modmenu's own mod container!"); + } + + return VersionUtil.removeBuildMetadata(container.get().getMetadata().getVersion().getFriendlyString()); + } +} diff --git a/src/main/java/com/terraformersmc/modmenu/util/JsonUtil.java b/src/main/java/com/terraformersmc/modmenu/util/JsonUtil.java new file mode 100644 index 00000000..287b0ec5 --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/util/JsonUtil.java @@ -0,0 +1,39 @@ +package com.terraformersmc.modmenu.util; + +import java.util.Optional; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +public class JsonUtil { + private JsonUtil() {} + + public static Optional getString(JsonObject parent, String field) { + if (!parent.has(field)) { + return Optional.empty(); + } + + JsonElement value = parent.get(field); + + if (!value.isJsonPrimitive() || !((JsonPrimitive)value).isString()) { + return Optional.empty(); + } + + return Optional.of(value.getAsString()); + } + + public static Optional getBoolean(JsonObject parent, String field) { + if (!parent.has(field)) { + return Optional.empty(); + } + + JsonElement value = parent.get(field); + + if (!value.isJsonPrimitive() || !((JsonPrimitive)value).isBoolean()) { + return Optional.empty(); + } + + return Optional.of(value.getAsBoolean()); + } +} diff --git a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java index 69014a64..81dc4ed0 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java +++ b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java @@ -7,17 +7,15 @@ import com.terraformersmc.modmenu.ModMenu; import com.terraformersmc.modmenu.api.UpdateChannel; import com.terraformersmc.modmenu.api.UpdateChecker; +import com.terraformersmc.modmenu.api.UpdateInfo; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.util.mod.Mod; import com.terraformersmc.modmenu.util.mod.ModrinthUpdateInfo; import net.fabricmc.loader.api.FabricLoader; import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -30,7 +28,6 @@ public class UpdateCheckerUtil { public static final Logger LOGGER = LogManager.getLogger("Mod Menu/Update Checker"); - private static final HttpClient client = HttpClientBuilder.create().build(); private static boolean modrinthApiV2Deprecated = false; private static boolean allowsUpdateChecks(Mod mod) { @@ -51,10 +48,21 @@ public static void checkForUpdates() { public static void checkForCustomUpdates() { ModMenu.MODS.values().stream().filter(UpdateCheckerUtil::allowsUpdateChecks).forEach(mod -> { UpdateChecker updateChecker = mod.getUpdateChecker(); + if (updateChecker == null) { return; } - UpdateCheckerThread.run(mod, () -> mod.setUpdateInfo(updateChecker.checkForUpdates())); + + UpdateCheckerThread.run(mod, () -> { + UpdateInfo update = updateChecker.checkForUpdates(); + + if (update == null) { + return; + } + + mod.setUpdateInfo(update); + LOGGER.info("Update available for '{}@{}'", mod.getId(), mod.getVersion()); + }); }); } @@ -80,16 +88,10 @@ public static void checkForModrinthUpdates() { } }); - String environment = ModMenu.devEnvironment ? "/development" : ""; - String primaryLoader = ModMenu.runningQuilt ? "quilt" : "fabric"; List loaders = ModMenu.runningQuilt ? Arrays.asList("fabric", "quilt") : Arrays.asList("fabric"); String mcVer = FabricLoader.getInstance().getModContainer("minecraft").get() .getMetadata().getVersion().getFriendlyString(); - String version = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID) - .get().getMetadata().getVersion().getFriendlyString(); - final String modMenuVersion = version.split("\\+", 2)[0]; // Strip build metadata for privacy - final String userAgent = String.format("%s/%s (%s/%s%s)", ModMenu.GITHUB_REF, modMenuVersion, mcVer, primaryLoader, environment); List updateChannels; UpdateChannel preferredChannel = UpdateChannel.getUserPreference(); @@ -104,18 +106,15 @@ public static void checkForModrinthUpdates() { String body = ModMenu.GSON_MINIFIED.toJson(new LatestVersionsFromHashesBody(modHashes.keySet(), loaders, mcVer, updateChannels)); - LOGGER.debug("User agent: " + userAgent); LOGGER.debug("Body: " + body); try { - HttpUriRequest latestVersionsRequest = RequestBuilder.post() + RequestBuilder latestVersionsRequest = RequestBuilder.post() .setEntity(new StringEntity(body)) - .addHeader("User-Agent", userAgent) .addHeader("Content-Type", "application/json") - .setUri(URI.create("https://api.modrinth.com/v2/version_files/update")) - .build(); + .setUri(URI.create("https://api.modrinth.com/v2/version_files/update")); - HttpResponse latestVersionsResponse = client.execute(latestVersionsRequest); + HttpResponse latestVersionsResponse = HttpUtil.request(latestVersionsRequest); int status = latestVersionsResponse.getStatusLine().getStatusCode(); LOGGER.debug("Status: " + status); diff --git a/src/main/java/com/terraformersmc/modmenu/util/VersionUtil.java b/src/main/java/com/terraformersmc/modmenu/util/VersionUtil.java index 140267cf..deb0f067 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/VersionUtil.java +++ b/src/main/java/com/terraformersmc/modmenu/util/VersionUtil.java @@ -6,9 +6,7 @@ public final class VersionUtil { private static final List PREFIXES = Arrays.asList("version", "ver", "v"); - private VersionUtil() { - return; - } + private VersionUtil() {} public static String stripPrefix(String version) { version = version.trim(); @@ -25,4 +23,8 @@ public static String stripPrefix(String version) { public static String getPrefixedVersion(String version) { return "v" + stripPrefix(version); } + + public static String removeBuildMetadata(String version) { + return version.split("\\+")[0]; + } } diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java new file mode 100644 index 00000000..726b842f --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java @@ -0,0 +1,152 @@ +package com.terraformersmc.modmenu.util.mod.fabric; + +import java.io.IOException; +import java.net.URI; +import java.util.Optional; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.terraformersmc.modmenu.api.UpdateChannel; +import com.terraformersmc.modmenu.api.UpdateChecker; +import com.terraformersmc.modmenu.api.UpdateInfo; +import com.terraformersmc.modmenu.util.HttpUtil; +import com.terraformersmc.modmenu.util.JsonUtil; +import com.terraformersmc.modmenu.util.OptionalUtil; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.SemanticVersion; +import net.fabricmc.loader.api.Version; +import net.fabricmc.loader.api.VersionParsingException; + +public class FabricLoaderUpdateChecker implements UpdateChecker { + public static final Logger LOGGER = LogManager.getLogger("Mod Menu/Fabric Update Checker"); + private static final URI LOADER_VERSIONS = URI.create("https://meta.fabricmc.net/v2/versions/loader"); + + @Override + public UpdateInfo checkForUpdates() { + UpdateInfo result = null; + + try { + result = checkForUpdates0(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (IOException e) { + LOGGER.error("Failed Fabric Loader update check!", e); + } + + return result; + } + + private static UpdateInfo checkForUpdates0() throws IOException, InterruptedException { + UpdateChannel preferredChannel = UpdateChannel.getUserPreference(); + + RequestBuilder request = RequestBuilder.get().setUri(LOADER_VERSIONS); + HttpResponse response = HttpUtil.request(request); + + int status = response.getStatusLine().getStatusCode(); + + if (status != 200) { + LOGGER.warn("Fabric Meta responded with a non-200 status: {}!", status); + return null; + } + + Header[] contentType = response.getHeaders("Content-Type"); + + if (contentType.length == 0 || !contentType[0].getValue().contains("application/json")) { + LOGGER.warn("Fabric Meta responded with a non-json content type, aborting loader update check!"); + return null; + } + + JsonElement data = new JsonParser().parse(EntityUtils.toString(response.getEntity())); + + if (!data.isJsonArray()) { + LOGGER.warn("Received invalid data from Fabric Meta, aborting loader update check!"); + return null; + } + + SemanticVersion match = null; + boolean stableVersion = true; + + for (JsonElement child : data.getAsJsonArray()) { + if (!child.isJsonObject()) { + continue; + } + + JsonObject object = child.getAsJsonObject(); + Optional version = JsonUtil.getString(object, "version"); + + if (!version.isPresent()) { + continue; + } + + SemanticVersion parsed; + + try { + parsed = SemanticVersion.parse(version.get()); + } catch (VersionParsingException e) { + continue; + } + + // Why aren't betas just marked as beta in the version string ... + boolean stable = OptionalUtil.isPresentAndTrue(JsonUtil.getBoolean(object, "stable")); + + if (preferredChannel == UpdateChannel.RELEASE && !stable) { + continue; + } + + if (match == null || isNewer(parsed, match)) { + match = parsed; + stableVersion = stable; + } + } + + Version current = getCurrentVersion(); + + if (match == null || !isNewer(match, current)) { + LOGGER.debug("Fabric Loader is up to date."); + return null; + } + + LOGGER.debug("Fabric Loader has a matching update available!"); + return new FabricLoaderUpdateInfo(stableVersion); + } + + private static boolean isNewer(Version self, Version other) { + return self.compareTo(other) > 0; + } + + private static Version getCurrentVersion() { + return FabricLoader.getInstance().getModContainer("fabricloader").get().getMetadata().getVersion(); + } + + private static class FabricLoaderUpdateInfo implements UpdateInfo { + private final boolean isStable; + + private FabricLoaderUpdateInfo(boolean isStable) { + this.isStable = isStable; + } + + @Override + public boolean isUpdateAvailable() { + return true; + } + + @Override + public String getDownloadLink() { + return "https://fabricmc.net/use/installer"; + } + + @Override + public UpdateChannel getUpdateChannel() { + return this.isStable ? UpdateChannel.RELEASE : UpdateChannel.BETA; + } + } +} diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java index 8ad3caf7..0169c333 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java @@ -53,7 +53,7 @@ public FabricMod(ModContainer modContainer, Set modpackMods) { this.container = modContainer; this.metadata = modContainer.getMetadata(); - if ("minecraft".equals(metadata.getId()) || "fabricloader".equals(metadata.getId()) || "java".equals(metadata.getId()) || "quilt_loader".equals(metadata.getId())) { + if ("minecraft".equals(metadata.getId()) || "java".equals(metadata.getId())) { allowsUpdateChecks = false; } diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java new file mode 100644 index 00000000..35dee024 --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java @@ -0,0 +1,162 @@ +package com.terraformersmc.modmenu.util.mod.quilt; + +import java.io.IOException; +import java.net.URI; +import java.util.Optional; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.quiltmc.loader.api.QuiltLoader; +import org.quiltmc.loader.api.Version; +import org.quiltmc.loader.api.VersionFormatException; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.terraformersmc.modmenu.api.UpdateChannel; +import com.terraformersmc.modmenu.api.UpdateChecker; +import com.terraformersmc.modmenu.api.UpdateInfo; +import com.terraformersmc.modmenu.util.HttpUtil; +import com.terraformersmc.modmenu.util.JsonUtil; + +public class QuiltLoaderUpdateChecker implements UpdateChecker { + public static final Logger LOGGER = LogManager.getLogger("Mod Menu/Quilt Update Checker"); + private static final URI LOADER_VERSIONS = URI.create("https://meta.quiltmc.org/v3/versions/loader"); + + @Override + public UpdateInfo checkForUpdates() { + UpdateInfo result = null; + + try { + result = checkForUpdates0(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (IOException e) { + LOGGER.error("Failed Quilt Loader update check!", e); + } + + return result; + } + + private static UpdateInfo checkForUpdates0() throws IOException, InterruptedException { + UpdateChannel preferredChannel = UpdateChannel.getUserPreference(); + + RequestBuilder request = RequestBuilder.get().setUri(LOADER_VERSIONS); + HttpResponse response = HttpUtil.request(request); + + int status = response.getStatusLine().getStatusCode(); + + if (status != 200) { + LOGGER.warn("Quilt Meta responded with a non-200 status: {}!", status); + return null; + } + + Header[] contentType = response.getHeaders("Content-Type"); + + if (contentType.length == 0 || !contentType[0].getValue().contains("application/json")) { + LOGGER.warn("Quilt Meta responded with a non-json content type, aborting loader update check!"); + return null; + } + + JsonElement data = new JsonParser().parse(EntityUtils.toString(response.getEntity())); + + if (!data.isJsonArray()) { + LOGGER.warn("Received invalid data from Quilt Meta, aborting loader update check!"); + return null; + } + + Version.Semantic match = null; + + for (JsonElement child : data.getAsJsonArray()) { + if (!child.isJsonObject()) { + continue; + } + + JsonObject object = child.getAsJsonObject(); + Optional version = JsonUtil.getString(object, "version"); + + if (!version.isPresent()) { + continue; + } + + Version.Semantic parsed; + + try { + parsed = Version.Semantic.of(version.get()); + } catch (VersionFormatException e) { + continue; + } + + if (preferredChannel == UpdateChannel.RELEASE && !parsed.preRelease().equals("")) { + continue; + } else if (preferredChannel == UpdateChannel.BETA && !isStableOrBeta(parsed.preRelease())) { + continue; + } + + if (match == null || isNewer(parsed, match)) { + match = parsed; + } + } + + Version.Semantic current = getCurrentVersion(); + + if (match == null || !isNewer(match, current)) { + LOGGER.debug("Quilt Loader is up to date."); + return null; + } + + LOGGER.debug("Quilt Loader has a matching update available!"); + + UpdateChannel updateChannel; + String preRelease = match.preRelease(); + + if (preRelease.isEmpty()) { + updateChannel = UpdateChannel.RELEASE; + } else if (isStableOrBeta(preRelease)) { + updateChannel = UpdateChannel.BETA; + } else { + updateChannel = UpdateChannel.ALPHA; + } + + return new QuiltLoaderUpdateInfo(updateChannel); + } + + private static boolean isNewer(Version.Semantic self, Version.Semantic other) { + return self.compareTo(other) > 0; + } + + private static Version.Semantic getCurrentVersion() { + return QuiltLoader.getModContainer("quilt_loader").get().metadata().version().semantic(); + } + + private static boolean isStableOrBeta(String preRelease) { + return preRelease.isEmpty() || preRelease.startsWith("beta") || preRelease.startsWith("pre") || preRelease.startsWith("rc"); + } + + private static class QuiltLoaderUpdateInfo implements UpdateInfo { + private final UpdateChannel updateChannel; + + private QuiltLoaderUpdateInfo(UpdateChannel updateChannel) { + this.updateChannel = updateChannel; + } + + @Override + public boolean isUpdateAvailable() { + return true; + } + + @Override + public String getDownloadLink() { + return "https://quiltmc.org/en/install/client"; + } + + @Override + public UpdateChannel getUpdateChannel() { + return this.updateChannel; + } + } +} From 67f816d56303942157765aff301d338f13ecee2a Mon Sep 17 00:00:00 2001 From: Prospector Date: Mon, 22 Apr 2024 13:26:20 -0700 Subject: [PATCH 14/23] Fix update checker not being called when screen is closed in a way other than the button - Fix update checker not being run when config screen is closed without the Done button --- .../com/terraformersmc/modmenu/gui/ModMenuOptionsScreen.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/terraformersmc/modmenu/gui/ModMenuOptionsScreen.java b/src/main/java/com/terraformersmc/modmenu/gui/ModMenuOptionsScreen.java index 9de7851a..cd1f4419 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/ModMenuOptionsScreen.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/ModMenuOptionsScreen.java @@ -66,6 +66,7 @@ public void buttonClicked(ButtonWidget button) { @Override public void removed() { + ModMenu.checkForUpdates(); ModMenuConfigManager.save(); } } From 45e80021385d773bcb0400aff77d189457efc368 Mon Sep 17 00:00:00 2001 From: LostLuma Date: Tue, 23 Apr 2024 03:58:03 +0200 Subject: [PATCH 15/23] Fix credits role translation keys not being lowercased --- .../modmenu/gui/widget/DescriptionListWidget.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java index b74f3296..baf9da7d 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java @@ -439,7 +439,7 @@ public void confirmResult(boolean result, int id) { private String creditsRoleText(String roleName) { // Replace spaces and dashes in role names with underscores if they exist // Notably Quilted Fabric API does this with FabricMC as "Upstream Owner" - String translationKey = roleName.replaceAll("[\\s-]", "_"); + String translationKey = roleName.replaceAll("[\\s-]", "_").toLowerCase(); return I18n.translate("modmenu.credits.role." + translationKey) + ":"; } From 713592f664b98911edea60a86ab7aff6a0e4e7b7 Mon Sep 17 00:00:00 2001 From: Lilly Rose Berner Date: Sat, 15 Jun 2024 16:58:59 +0200 Subject: [PATCH 16/23] Remove sorting of individual contributors (#726) - Fixed contributors being sorted --- .../modmenu/gui/widget/DescriptionListWidget.java | 6 +++--- .../com/terraformersmc/modmenu/util/mod/Mod.java | 2 +- .../util/mod/fabric/FabricDummyParentMod.java | 2 +- .../modmenu/util/mod/fabric/FabricMod.java | 9 +++++---- .../modmenu/util/mod/quilt/QuiltMod.java | 15 +++++++-------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java index baf9da7d..c22253ec 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java @@ -217,7 +217,7 @@ public void render(int mouseX, int mouseY, float delta) { this.entries.add(new MojangCreditsEntry((String) line)); } } else if (!"java".equals(mod.getId())) { - SortedMap> credits = mod.getCredits(); + SortedMap> credits = mod.getCredits(); if (!credits.isEmpty()) { this.entries.add(emptyEntry); @@ -226,12 +226,12 @@ public void render(int mouseX, int mouseY, float delta) { this.entries.add(new DescriptionEntry((String) line)); } - Iterator>> iterator = credits.entrySet().iterator(); + Iterator>> iterator = credits.entrySet().iterator(); while (iterator.hasNext()) { int indent = 8; - Map.Entry> role = iterator.next(); + Map.Entry> role = iterator.next(); String roleName = role.getKey(); for (Object line : textRenderer.split(this.creditsRoleText(roleName), wrapWidth - 16)) { diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java index d5e1d210..8fa5a4af 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java @@ -80,7 +80,7 @@ default String getTranslatedDescription() { * @return a mapping of roles to each contributor with that role. */ @NotNull - SortedMap> getCredits(); + SortedMap> getCredits(); @NotNull Set getBadges(); diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java index c1cf2229..0efceefd 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java @@ -95,7 +95,7 @@ public FabricDummyParentMod(FabricMod host, String id) { } @Override - public @NotNull SortedMap> getCredits() { + public @NotNull SortedMap> getCredits() { return new TreeMap<>(); } diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java index 0169c333..aeb880a5 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java @@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.lwjgl.system.CallbackI.V; import java.awt.image.BufferedImage; import java.io.File; @@ -210,7 +211,7 @@ public FabricMod(ModContainer modContainer, Set modpackMods) { @Override public @NotNull Map> getContributors() { - Map> contributors = new HashMap<>(); + Map> contributors = new LinkedHashMap<>(); for (Person contributor : this.metadata.getContributors()) { contributors.put(contributor.getName(), Arrays.asList("Contributor")); @@ -220,8 +221,8 @@ public FabricMod(ModContainer modContainer, Set modpackMods) { } @Override - public @NotNull SortedMap> getCredits() { - SortedMap> credits = new TreeMap<>(); + public @NotNull SortedMap> getCredits() { + SortedMap> credits = new TreeMap<>(); List authors = this.getAuthors(); Map> contributors = this.getContributors(); @@ -232,7 +233,7 @@ public FabricMod(ModContainer modContainer, Set modpackMods) { for (Map.Entry> contributor : contributors.entrySet()) { for (String role : contributor.getValue()) { - credits.computeIfAbsent(role, key -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); + credits.computeIfAbsent(role, key -> new LinkedHashSet<>()); credits.get(role).add(contributor.getKey()); } } diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java index 9a967c17..9fd415fc 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java @@ -16,17 +16,16 @@ import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.SortedMap; -import java.util.SortedSet; import java.util.TreeMap; -import java.util.TreeSet; import java.util.stream.Collectors; public class QuiltMod extends FabricMod { @@ -61,7 +60,7 @@ public QuiltMod(net.fabricmc.loader.api.ModContainer fabricModContainer, Set> getContributors() { - Map> contributors = new HashMap<>(); + Map> contributors = new LinkedHashMap<>(); for (ModContributor contributor : this.metadata.contributors()) { contributors.put(contributor.name(), contributor.roles()); @@ -71,14 +70,14 @@ public QuiltMod(net.fabricmc.loader.api.ModContainer fabricModContainer, Set> getCredits() { - SortedMap> credits = new TreeMap<>(); + public @NotNull SortedMap> getCredits() { + SortedMap> credits = new TreeMap<>(); Map> contributors = this.getContributors(); for (Map.Entry> contributor : contributors.entrySet()) { for (String role : contributor.getValue()) { - credits.computeIfAbsent(role, key -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); + credits.computeIfAbsent(role, key -> new LinkedHashSet<>()); credits.get(role).add(contributor.getKey()); } } From 6dfa7c71a9cae5c17f676828d7610e6b20fd0181 Mon Sep 17 00:00:00 2001 From: Starmoe Date: Sat, 15 Jun 2024 23:03:19 +0800 Subject: [PATCH 17/23] Update zh_cn.json (#733) - Update Chinese Translation --- .../resources/assets/modmenu/lang/zh_CN.lang | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/resources/assets/modmenu/lang/zh_CN.lang b/src/main/resources/assets/modmenu/lang/zh_CN.lang index 47d5d3bb..3c1d0ad2 100644 --- a/src/main/resources/assets/modmenu/lang/zh_CN.lang +++ b/src/main/resources/assets/modmenu/lang/zh_CN.lang @@ -1,4 +1,3 @@ -{ category.modmenu.name=模组菜单 key.modmenu.open_menu=打开模组菜单 modmenu.title=模组 @@ -44,8 +43,7 @@ modmenu.modIdToolTip=模组 ID:%s modmenu.authorPrefix=作者:%s modmenu.config=编辑配置 modmenu.configure=配置…… -modmenu.configure.error=无法加载“%s”的配置屏幕 -请报告给“%s”,而非Mod Menu +modmenu.configure.error=无法加载“%s”的配置屏幕\n请报告给“%s”,而非Mod Menu modmenu.website=网站 modmenu.issues=问题 modmenu.credits=模组贡献者: @@ -57,6 +55,7 @@ modmenu.hasUpdate=有更新可用: modmenu.experimental=(更新检测器处于实验阶段!) modmenu.childHasUpdate=此模组的一个子模组有更新。 modmenu.updateText=%2$s中的v%1$s +modmenu.downloadLink=下载 modmenu.buymeacoffee=给我买一杯咖啡 modmenu.coindrop=Coindrop modmenu.crowdin=Crowdin @@ -78,6 +77,13 @@ modmenu.twitch=Twitch modmenu.twitter=推特(Twitter) modmenu.wiki=百科 modmenu.youtube=YouTube +modmenu.credits.role.author=作者 +modmenu.credits.role.contributor=贡献者 +modmenu.credits.role.translator=翻译者 +modmenu.credits.role.maintainer=维护者 +modmenu.credits.role.playtester=游戏测试员 +modmenu.credits.role.illustrator=画师 +modmenu.credits.role.owner=所有者 modmenu.modsFolder=打开模组文件夹 modmenu.configsFolder=打开配置文件夹 modmenu.nameTranslation.minecraft=Minecraft @@ -153,4 +159,8 @@ option.modmenu.button_update_badge.true=显示 option.modmenu.button_update_badge.false=隐藏 option.modmenu.quick_configure=快速配置 option.modmenu.quick_configure.true=启用 -option.modmenu.quick_configure.false=禁用 +option.modmenu.quick_configure.false=禁用" +option.modmenu.update_channel=更新频道 +option.modmenu.update_channel.alpha=全部 +option.modmenu.update_channel.beta=正式版和测试版 +option.modmenu.update_channel.release=仅正式版 From 39a57510a3f41a5cc07b7d7d6cd137d8e09bf4d8 Mon Sep 17 00:00:00 2001 From: Amirhan-Taipovjan-Greatest-I <51203385+Amirhan-Taipovjan-Greatest-I@users.noreply.github.com> Date: Sat, 15 Jun 2024 18:04:17 +0300 Subject: [PATCH 18/23] Updated Tatar Translation! (#715) - Updated Tatar Translation --- .../resources/assets/modmenu/lang/tt_RU.lang | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/resources/assets/modmenu/lang/tt_RU.lang b/src/main/resources/assets/modmenu/lang/tt_RU.lang index 570f8fa2..6a39faab 100644 --- a/src/main/resources/assets/modmenu/lang/tt_RU.lang +++ b/src/main/resources/assets/modmenu/lang/tt_RU.lang @@ -3,6 +3,7 @@ key.modmenu.open_menu=Мод исемлеген ачу modmenu.title=Модлар modmenu.nameTranslation.modmenu=Mod Menu modmenu.descriptionTranslation.modmenu=Утыртылган модлар исемлеген карау өчен мод менюсын өсти. + modmenu.loaded=(%s йөкләнгән) modmenu.loaded.short=(%s) modmenu.loaded.69.secret=(%s йөкләнгән... шәп) @@ -11,6 +12,7 @@ modmenu.mods.n= (%s мод) modmenu.mods.1= (%s мод) modmenu.mods.69.secret= (%s мод... шәп) modmenu.mods.420.secret= (%s мод... баш әйләнә башлады...) + modmenu.search=Модлар эзләү modmenu.searchTerms.library=api апи апиай китапханә modmenu.searchTerms.patchwork=patchwork forge fml фордж форҗ форж патчворк фмл патчуорк @@ -19,6 +21,7 @@ modmenu.searchTerms.deprecated=искергән иске борынгы әүвә modmenu.searchTerms.clientside=клиент клиентлы modmenu.searchTerms.configurable=көйләүләр көйләнелә торган көйләнмә көйләү көйләнеш modmenu.searchTerms.hasUpdate=яңартулар версия яңарту + modmenu.toggleFilterOptions=Фильтрлар modmenu.showingMods.n=%s мод күрсәтелә modmenu.showingMods.1=%s мод күрсәтелә @@ -28,25 +31,28 @@ modmenu.showingModsLibraries.n.n=%s мод һәм %s китапханә күрс modmenu.showingModsLibraries.n.1=%s мод һәм %s китапханә күрсәтелә modmenu.showingModsLibraries.1.n=%s мод һәм %s китапханә күрсәтелә modmenu.showingModsLibraries.1.1=%s мод һәм %s китапханә күрсәтелә + modmenu.badge.library=Китапханә modmenu.badge.clientsideOnly=Клиент modmenu.badge.deprecated=Искергән modmenu.badge.forge=Forge modmenu.badge.modpack=Мод җыелмасы modmenu.badge.minecraft=Minecraft + modmenu.dropInfo.line1=Модларны өстәү өчен файлларны modmenu.dropInfo.line2=бу тәрәзәгә күчерегез modmenu.dropConfirm=Бу модларны мод папкасына күчереп алырга телисезме? modmenu.dropSuccessful.line1=Модлар күчереп алынды modmenu.dropSuccessful.line2=Модлар эшләү өчен уенны яңадан кушыгыз + modmenu.modIdToolTip=Модның ID: %s modmenu.authorPrefix=Автор(лар): %s modmenu.config=Көйләүләрне үзгәртү modmenu.configure=Көйләүләр... -modmenu.configure.error=«%s» көйләүләрен йөкләп булмады -Mod Menu түгел, ә «%s» автор(лар)ына хәбәр итегез +modmenu.configure.error=«%s» көйләүләрен йөкләп булмады\nMod Menu түгел, ә «%s» автор(лар)ына хәбәр итегез modmenu.website=Сайт modmenu.issues=Мәсьәләләр + modmenu.credits=Автор(лар): modmenu.viewCredits=Автор(лар) күрсәтү modmenu.license=Лицензия: @@ -56,6 +62,8 @@ modmenu.hasUpdate=Яңарту бар: modmenu.experimental=(Mod Menu яңарту тикшерүчесе — эксперименталь функция!) modmenu.childHasUpdate=Бу модның бала өчен яңарту бар. modmenu.updateText=%2$s сайтында %1$s версиясе +modmenu.downloadLink=Йөкләнү + modmenu.buymeacoffee=Buy Me a Coffee modmenu.coindrop=Coindrop modmenu.crowdin=Crowdin @@ -77,13 +85,24 @@ modmenu.twitch=Twitch modmenu.twitter=Twitter modmenu.wiki=Вики modmenu.youtube=YouTube + +modmenu.credits.role.author=Авторлар +modmenu.credits.role.contributor=Катнашучылар +modmenu.credits.role.translator=Тәрҗемәчеләр +modmenu.credits.role.maintainer=Дәвам иттерүчеләр +modmenu.credits.role.playtester=Уен тикшерүчеләре +modmenu.credits.role.illustrator=Иллюстраторлар +modmenu.credits.role.owner=Хуҗалар + modmenu.modsFolder=Модлар папкасы modmenu.configsFolder=Көйләүләр папкасы + modmenu.nameTranslation.minecraft=Minecraft modmenu.descriptionTranslation.minecraft=Төп уен. modmenu.nameTranslation.java=Java modmenu.descriptionTranslation.java=Java үтәү мохите. modmenu.javaDistributionName=Кабызылган: %s + modmenu.options=Мод исемлегенең көйләүләре option.modmenu.sorting=Тәртипләү option.modmenu.sorting.ascending=A-Z @@ -153,3 +172,7 @@ option.modmenu.button_update_badge.false=Яшерен option.modmenu.quick_configure=Тиз көйләүләр option.modmenu.quick_configure.true=Кушык option.modmenu.quick_configure.false=Сүнек +option.modmenu.update_channel=Update Channel +option.modmenu.update_channel.alpha=All +option.modmenu.update_channel.beta=Release & Beta +option.modmenu.update_channel.release=Release From 59436afcbd4f06ec237759d51ee995cb49f8b617 Mon Sep 17 00:00:00 2001 From: Damian <34820479+mt1006@users.noreply.github.com> Date: Sat, 15 Jun 2024 17:05:46 +0200 Subject: [PATCH 19/23] Update pl_pl.json (#717) - Updated Polish Translation --- .../resources/assets/modmenu/lang/pl_PL.lang | 100 ++++++++++++------ 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/src/main/resources/assets/modmenu/lang/pl_PL.lang b/src/main/resources/assets/modmenu/lang/pl_PL.lang index 3debeb2d..2e0df22b 100644 --- a/src/main/resources/assets/modmenu/lang/pl_PL.lang +++ b/src/main/resources/assets/modmenu/lang/pl_PL.lang @@ -3,21 +3,25 @@ key.modmenu.open_menu=Otwórz menu modów modmenu.title=Mody modmenu.nameTranslation.modmenu=Menu Modów modmenu.descriptionTranslation.modmenu=Dodaje menu modów umożliwiające wyświetlenie listy z zainstalowanymi modami. + modmenu.loaded=(%s załadowanych) modmenu.loaded.short=(%s) modmenu.loaded.69.secret=(%s załadowanych... nieźle) -modmenu.loaded.420.secret=(%s załadowane... jesteś tego pewien?) +modmenu.loaded.420.secret=(%s załadowane... jesteś tego pewien?) modmenu.mods.n= (%s Modów) modmenu.mods.1= (%s Mod) modmenu.mods.69.secret= (%s modów...nieźle) -modmenu.mods.420.secret= (%s modów... jesteś tego pewien?) +modmenu.mods.420.secret= (%s modów... jesteś tego pewien?) + modmenu.search=Szukaj modów -modmenu.searchTerms.library=biblioteka api +modmenu.searchTerms.library=api biblioteka modmenu.searchTerms.patchwork=patchwork forge fml -modmenu.searchTerms.modpack=paczka modpacków +modmenu.searchTerms.modpack=modpack paczka modmenu.searchTerms.deprecated=przestarzały nieaktualny stary -modmenu.searchTerms.clientside=po stronie klienta -modmenu.searchTerms.configurable=konfiguracja +modmenu.searchTerms.clientside=klient klienta clientside +modmenu.searchTerms.configurable=konfiguracja konfiguracje konfigurowalny ustawienia config +modmenu.searchTerms.hasUpdate=aktualizacja aktualizacje wersja updates + modmenu.toggleFilterOptions=Zmień ustawienia filtrów modmenu.showingMods.n=Pokazuję %s modyfikacji modmenu.showingMods.1=Pokazuję %s modyfikację @@ -27,33 +31,39 @@ modmenu.showingModsLibraries.n.n=Pokazuję %s modyfikacji oraz %s bibliotek modmenu.showingModsLibraries.n.1=Pokazuję %s modyfikacji oraz %s bibliotekę modmenu.showingModsLibraries.1.n=Pokazuję %s modyfikację oraz %s bibliotek modmenu.showingModsLibraries.1.1=Pokazuję %s modyfikację oraz %s bibliotekę + modmenu.badge.library=Biblioteka modmenu.badge.clientsideOnly=Klient -modmenu.badge.deprecated=Porzucone +modmenu.badge.deprecated=Przestarzały modmenu.badge.forge=Forge modmenu.badge.modpack=Paczka modów modmenu.badge.minecraft=Minecraft + modmenu.dropInfo.line1=Przeciągnij i upuść mody modmenu.dropInfo.line2=w to okno, aby je dodać modmenu.dropConfirm=Czy chcesz skopiować wybrane mody do folderu z modami? modmenu.dropSuccessful.line1=Mody zostały pomyślnie skopiowane modmenu.dropSuccessful.line2=Zrestartuj grę, aby załadować mody + modmenu.modIdToolTip=ID moda: %s modmenu.authorPrefix=Stworzone przez %s modmenu.config=Edytuj konfigurację modmenu.configure=Skonfiguruj... -modmenu.configure.error=Nie udało się załadować ekranu konfiguracyjnego dla '%s' -Raport do '%s', a nie menu modyfikacji +modmenu.configure.error=Nie udało się załadować ekranu konfiguracyjnego dla '%s'\nZgłoś do '%s', a nie Menu Modów modmenu.website=Strona modmenu.issues=Błędy + modmenu.credits=Autorzy: modmenu.viewCredits=Pokaż napisy końcowe modmenu.license=Licencja: modmenu.links=Linki: modmenu.source=Kod źródłowy modmenu.hasUpdate=Aktualizacja jest dostępna: -modmenu.childHasUpdate=Dziecko tego modu ma dostępną aktualizację. +modmenu.experimental=(Funkcja sprawdzania aktualizacji jest eksperymentalna!) +modmenu.childHasUpdate=Mod potomny tego modu ma dostępną aktualizację. modmenu.updateText=v%s na %s +modmenu.downloadLink=Pobierz + modmenu.buymeacoffee=Kup Autorowi Kawę modmenu.coindrop=Coindrop modmenu.crowdin=Crowdin @@ -72,75 +82,97 @@ modmenu.patreon=Patreon modmenu.paypal=PayPal modmenu.reddit=Reddit modmenu.twitch=Twitch -modmenu.twitter=Twitter +modmenu.twitter=X (Twitter) modmenu.wiki=Dokumentacja modmenu.youtube=YouTube + +modmenu.credits.role.author=Autorzy +modmenu.credits.role.contributor=Kontrybutorzy +modmenu.credits.role.translator=Tłumacze +modmenu.credits.role.maintainer=Opiekunowie +modmenu.credits.role.playtester=Testerzy +modmenu.credits.role.illustrator=Ilustratorzy +modmenu.credits.role.owner=Właściciele + modmenu.modsFolder=Otwórz folder z modami modmenu.configsFolder=Otwórz folder z plikami konfiguracyjnymi + modmenu.nameTranslation.minecraft=Minecraft modmenu.descriptionTranslation.minecraft=Gra podstawowa. modmenu.nameTranslation.java=Java modmenu.descriptionTranslation.java=Środowisko wykonawcze Java. modmenu.javaDistributionName=Uruchomione: %s + modmenu.options=Opcje Mod Menu -option.modmenu.sorting=Sortuj: %s +option.modmenu.sorting=Sortuj option.modmenu.sorting.ascending=A-Z option.modmenu.sorting.descending=Z-A -option.modmenu.show_libraries=Biblioteki: %s +option.modmenu.show_libraries=Biblioteki option.modmenu.show_libraries.true=Pokazuj option.modmenu.show_libraries.false=Ukrywaj -option.modmenu.hide_config_buttons=Przyciski konfiguracji: %s +option.modmenu.hide_config_buttons=Przyciski konfiguracji option.modmenu.hide_config_buttons.true=Ukrywaj option.modmenu.hide_config_buttons.false=Pokazuj -option.modmenu.hide_badges=Odznaki modów: %s +option.modmenu.hide_badges=Odznaki modów option.modmenu.hide_badges.true=Ukrywaj option.modmenu.hide_badges.false=Pokazuj -option.modmenu.compact_list=Lista: %s +option.modmenu.compact_list=Lista option.modmenu.compact_list.true=Kompaktowa option.modmenu.compact_list.false=Standardowa -option.modmenu.hide_mod_links=Linki modów: %s +option.modmenu.hide_mod_links=Linki modów option.modmenu.hide_mod_links.true=Ukrywaj option.modmenu.hide_mod_links.false=Pokazuj -option.modmenu.hide_mod_credits=Autorzy modów: %s +option.modmenu.hide_mod_credits=Autorzy modów option.modmenu.hide_mod_credits.true=Ukrywaj option.modmenu.hide_mod_credits.false=Pokazuj -option.modmenu.hide_mod_license=Licencje modów: %s +option.modmenu.hide_mod_license=Licencje modów option.modmenu.hide_mod_license.true=Ukrywaj option.modmenu.hide_mod_license.false=Pokazuj -option.modmenu.count_children=Dzieci: %s +option.modmenu.count_children=Mody potomne option.modmenu.count_children.true=Liczone option.modmenu.count_children.false=Nieliczone -option.modmenu.count_libraries=Biblioteki: %s +option.modmenu.count_libraries=Biblioteki option.modmenu.count_libraries.true=Liczone option.modmenu.count_libraries.false=Nieliczone -option.modmenu.count_hidden_mods=Ukryte mody: %s +option.modmenu.count_hidden_mods=Ukryte mody option.modmenu.count_hidden_mods.true=Liczone option.modmenu.count_hidden_mods.false=Nieliczone -option.modmenu.mod_count_location=Licznik modów: %s +option.modmenu.mod_count_location=Licznik modów option.modmenu.mod_count_location.title_screen=W narożniku option.modmenu.mod_count_location.mods_button=Na przycisku option.modmenu.mod_count_location.title_screen_and_mods_button=Tu i tu option.modmenu.mod_count_location.none=Nigdzie -option.modmenu.easter_eggs=Easter Eggi: %s +option.modmenu.easter_eggs=Easter Eggi option.modmenu.easter_eggs.true=Włączone option.modmenu.easter_eggs.false=Wyłączone -option.modmenu.mods_button_style=Na przycisku +option.modmenu.mods_button_style=Na ekranie tytułowym option.modmenu.mods_button_style.classic=Poniżej Realms option.modmenu.mods_button_style.replace_realms=Zastąp Realms option.modmenu.mods_button_style.shrink=Obok Realms -option.modmenu.mods_button_style.icon=Ikona -option.modmenu.random_java_colors=Kolor Java -option.modmenu.random_java_colors.true=Sprzedawca +option.modmenu.mods_button_style.icon=Ikonka +option.modmenu.game_menu_button_style=W menu gry +option.modmenu.game_menu_button_style.below_bugs=Poniżej błędów +option.modmenu.game_menu_button_style.replace_bugs=Zastąp błędy +option.modmenu.game_menu_button_style.icon=Ikonka +option.modmenu.random_java_colors=Kolor Javy +option.modmenu.random_java_colors.true=Dostawca option.modmenu.random_java_colors.false=Zawsze czerwony option.modmenu.translate_names=Nazwy -option.modmenu.translate_names.true=Zlokalizowane -option.modmenu.translate_names.false=Nie Zlokalizowano -option.modmenu.translate_descriptions=Opis -option.modmenu.translate_descriptions.true=Zlokalizowane -option.modmenu.translate_descriptions.false=Nie Zlokalizowano +option.modmenu.translate_names.true=Przetłumaczone +option.modmenu.translate_names.false=Nieprzetłumaczone +option.modmenu.translate_descriptions=Opisy +option.modmenu.translate_descriptions.true=Przetłumaczone +option.modmenu.translate_descriptions.false=Nieprzetłumaczone option.modmenu.update_checker=Sprawdzanie aktualizacji option.modmenu.update_checker.true=Włączone option.modmenu.update_checker.false=Wyłączone -option.modmenu.button_update_badge=Zaktualizuj wskaźnik +option.modmenu.button_update_badge=Wskaźnik aktualizacji option.modmenu.button_update_badge.true=Pokazuj option.modmenu.button_update_badge.false=Ukrywaj +option.modmenu.quick_configure=Szybka konfiguracja +option.modmenu.quick_configure.true=Włączona +option.modmenu.quick_configure.false=Wyłączona +option.modmenu.update_channel=Kanał aktualizacji +option.modmenu.update_channel.alpha=Wszystkie +option.modmenu.update_channel.beta=Wydania i bety +option.modmenu.update_channel.release=Wydania From 5d7ee88f559509f1c79d72aaacf2c142d7e51b41 Mon Sep 17 00:00:00 2001 From: Alex Gazmanovich <128359229+Gazmanovich@users.noreply.github.com> Date: Sat, 15 Jun 2024 18:06:40 +0300 Subject: [PATCH 20/23] Update be_by.json (#728) - Updated Belarusian Translation --- .../resources/assets/modmenu/lang/be_BY.lang | 324 ++++++++++-------- 1 file changed, 178 insertions(+), 146 deletions(-) diff --git a/src/main/resources/assets/modmenu/lang/be_BY.lang b/src/main/resources/assets/modmenu/lang/be_BY.lang index c51ce954..0ef4b7ff 100644 --- a/src/main/resources/assets/modmenu/lang/be_BY.lang +++ b/src/main/resources/assets/modmenu/lang/be_BY.lang @@ -1,146 +1,178 @@ -category.modmenu.nameMod Menu -key.modmenu.open_menuАдкрыць Mod Menu -modmenu.titleМоды -modmenu.nameTranslation.modmenuMod Menu -modmenu.descriptionTranslation.modmenuДадае меню модаў для прагляду спіса модаў, якія ў вас усталяваны. -modmenu.loaded(%s загружана) -modmenu.loaded.short(%s) -modmenu.loaded.69.secret(%s загружана...файна) -modmenu.loaded.420.secret(%s загружана...трасца) -modmenu.mods.n (%s моды) -modmenu.mods.1 (%s мод) -modmenu.mods.69.secret (%s модаў...файна) -modmenu.mods.420.secret (%s модаў...трасца) -modmenu.searchПошук модаў -modmenu.searchTerms.libraryAPI бібліятэка -modmenu.searchTerms.patchworkpatchwork forge fml -modmenu.searchTerms.modpackзборка зборка -modmenu.searchTerms.deprecatedсастарэлы неактуальны стары -modmenu.searchTerms.clientsideбок-кліента бок-гульні -modmenu.searchTerms.configurableНалады наладжваемый насладжваемыя опцыі -modmenu.toggleFilterOptionsУключыць фільтр -modmenu.showingMods.nПаказваецца %s модаў -modmenu.showingMods.1паказваецца %s мод -modmenu.showingLibraries.nПаказана %s бібліятэк -modmenu.showingLibraries.1Паказана %s бібліятэка -modmenu.showingModsLibraries.n.nПаказана %s модаў і %s бібліятэк -modmenu.showingModsLibraries.n.1Паказана %s модаў і %s бібліятэка -modmenu.showingModsLibraries.1.nПаказана %s мод і %s бібліятэк -modmenu.showingModsLibraries.1.1Паказана %s мод і %s бібліятэка -modmenu.badge.libraryБібліятэка -modmenu.badge.clientsideOnlyКліент -modmenu.badge.deprecatedСастарэлы -modmenu.badge.forgeForge -modmenu.badge.modpackЗборка -modmenu.badge.minecraftMinecraft -modmenu.dropInfo.line1Перацягніце файлу ў -modmenu.dropInfo.line2гэта акно, каб дадаць моды -modmenu.dropConfirmВы хаціце скапіяваць наступныя моды у вашу папку mods? -modmenu.dropSuccessful.line1Моды скапіяваны паспяхова -modmenu.dropSuccessful.line2Перазапусціце гульню, каб загрузіць моды -modmenu.modIdToolTipМод ID: %s -modmenu.authorPrefixад %s -modmenu.configЗмяніць налады -modmenu.configureНаладзіць... -modmenu.configure.errorНемагчыма загрузіць экран налад для '%s' -Паведаміце '%s', не Mod Menu -modmenu.websiteСайт -modmenu.issuesПажаданні -modmenu.creditsАўтары: -modmenu.viewCreditsПаглядзець аўтараў -modmenu.licenseЛіцэнзія: -modmenu.linksСпасылкі: -modmenu.sourceКрыніца -modmenu.hasUpdateДаступна абнаўленне: -modmenu.childHasUpdateАбнаўленне дзіцячага мода даступна. -modmenu.updateTextv%s на %s -modmenu.buymeacoffeeBuy Me a Coffee -modmenu.coindropCoindrop -modmenu.crowdinCrowdin -modmenu.curseforgeCurseForge -modmenu.discordDiscord -modmenu.donateDonate -modmenu.flattrFlattr -modmenu.github_releasesGitHub рэлізы -modmenu.github_sponsorsGitHub спонсары -modmenu.kofiKo-fi -modmenu.liberapayLiberapay -modmenu.mastodonMastodon -modmenu.modrinthModrinth -modmenu.opencollectiveOpen Collective -modmenu.patreonPatreon -modmenu.paypalPayPal -modmenu.redditReddit -modmenu.twitchTwitch -modmenu.twitterX (былы Twitter) -modmenu.wikiWiki -modmenu.youtubeYouTube -modmenu.modsFolderАдкрыць папку модаў -modmenu.configsFolderАдкрыць папку налад -modmenu.nameTranslation.minecraftMinecraft -modmenu.descriptionTranslation.minecraftБазавая гульня. -modmenu.nameTranslation.javaJava -modmenu.descriptionTranslation.javaБягучае асяроддзе Java. -modmenu.javaDistributionNameБягучая: %s -modmenu.optionsНалады Mod Menu -option.modmenu.sortingСартыроўка -option.modmenu.sorting.ascendingA-Z -option.modmenu.sorting.descendingZ-A -option.modmenu.show_librariesБібліятэкі -option.modmenu.show_libraries.trueПаказаны -option.modmenu.show_libraries.falseСхаваны -option.modmenu.hide_config_buttonsКнопка налад -option.modmenu.hide_config_buttons.trueСхавана -option.modmenu.hide_config_buttons.falseПаказана -option.modmenu.hide_badgesІконкі модаў -option.modmenu.hide_badges.trueСхаваны -option.modmenu.hide_badges.falseПаказаны -option.modmenu.compact_listСпіс -option.modmenu.compact_list.trueКампактны -option.modmenu.compact_list.falseСтандартны -option.modmenu.hide_mod_linksСпасылкі модаў -option.modmenu.hide_mod_links.trueСхаваны -option.modmenu.hide_mod_links.falseПаказаны -option.modmenu.hide_mod_creditsАЎтары модаў -option.modmenu.hide_mod_credits.trueСхаваны -option.modmenu.hide_mod_credits.falseПаказаны -option.modmenu.hide_mod_licenseЛіцэнзія модаў -option.modmenu.hide_mod_license.trueСхавана -option.modmenu.hide_mod_license.falseПаказана -option.modmenu.count_childrenДзіцячыя моды -option.modmenu.count_children.trueУлічваюцца -option.modmenu.count_children.falseНе улічваюцца -option.modmenu.count_librariesБібліятэкі -option.modmenu.count_libraries.trueУлічваюцца -option.modmenu.count_libraries.falseНе ўлічваюцца -option.modmenu.count_hidden_modsСхаваныя моды -option.modmenu.count_hidden_mods.trueУлічваюцца -option.modmenu.count_hidden_mods.falseНе ўлічваюцца -option.modmenu.mod_count_locationЛічба модаў -option.modmenu.mod_count_location.title_screenВугал экрана -option.modmenu.mod_count_location.mods_buttonКнопка модаў -option.modmenu.mod_count_location.title_screen_and_mods_buttonАбодва -option.modmenu.mod_count_location.noneНіяк -option.modmenu.easter_eggsСакрэты -option.modmenu.easter_eggs.trueУключаны -option.modmenu.easter_eggs.falseАдключаны -option.modmenu.mods_button_styleКнопка модаў -option.modmenu.mods_button_style.classicНіжэй Realms -option.modmenu.mods_button_style.replace_realmsЗамест Realms -option.modmenu.mods_button_style.shrinkПобач -option.modmenu.mods_button_style.iconІконкі -option.modmenu.random_java_colorsJava колер -option.modmenu.random_java_colors.trueАд распрацоўшчыка -option.modmenu.random_java_colors.falseЗаўсёды чырвоны -option.modmenu.translate_namesНазвы -option.modmenu.translate_names.trueЛакалізаваны -option.modmenu.translate_names.falseНе лакалізаваны -option.modmenu.translate_descriptionsАпісання -option.modmenu.translate_descriptions.trueЛакалізаваныя -option.modmenu.translate_descriptions.falseНе лакалізаваныя -option.modmenu.update_checkerПраверка абнаўленняў -option.modmenu.update_checker.trueУключана -option.modmenu.update_checker.falseАдключана -option.modmenu.button_update_badgeІндыкатар абнаўленняў -option.modmenu.button_update_badge.trueПаказаны -option.modmenu.button_update_badge.falseСхаваны +category.modmenu.name=Mod Menu +key.modmenu.open_menu=Адкрыць Mod Menu +modmenu.title=Моды +modmenu.nameTranslation.modmenu=Mod Menu +modmenu.descriptionTranslation.modmenu=Дадае меню модаў для прагляду спіса модаў, якія ў вас усталяваны. + +modmenu.loaded=(%s загружана) +modmenu.loaded.short=(%s) +modmenu.loaded.69.secret=(%s загружана...файна) +modmenu.loaded.420.secret=(%s загружана...трасца) +modmenu.mods.n= (%s моды) +modmenu.mods.1= (%s мод) +modmenu.mods.69.secret= (%s модаў...файна) +modmenu.mods.420.secret= (%s модаў...дымна) + +modmenu.search=Пошук модаў +modmenu.searchTerms.library=API бібліятэка +modmenu.searchTerms.patchwork=patchwork forge fml +modmenu.searchTerms.modpack=зборка зборка +modmenu.searchTerms.deprecated=састарэлы неактуальны стары +modmenu.searchTerms.clientside=бок-кліента бок-гульні +modmenu.searchTerms.configurable=Налады наладжваемый насладжваемыя опцыі +modmenu.searchTerms.hasUpdate=абнаўленні версія + +modmenu.toggleFilterOptions=Уключыць фільтр +modmenu.showingMods.n=Паказваецца %s модаў +modmenu.showingMods.1=паказваецца %s мод +modmenu.showingLibraries.n=Паказана %s бібліятэк +modmenu.showingLibraries.1=Паказана %s бібліятэка +modmenu.showingModsLibraries.n.n=Паказана %s модаў і %s бібліятэк +modmenu.showingModsLibraries.n.1=Паказана %s модаў і %s бібліятэка +modmenu.showingModsLibraries.1.n=Паказана %s мод і %s бібліятэк +modmenu.showingModsLibraries.1.1=Паказана %s мод і %s бібліятэка + +modmenu.badge.library=Бібліятэка +modmenu.badge.clientsideOnly=Кліент +modmenu.badge.deprecated=Састарэлы +modmenu.badge.forge=Forge +modmenu.badge.modpack=Зборка +modmenu.badge.minecraft=Minecraft + +modmenu.dropInfo.line1=Перацягніце файлу ў +modmenu.dropInfo.line2=гэта акно, каб дадаць моды +modmenu.dropConfirm=Вы хаціце скапіяваць наступныя моды у вашу папку mods? +modmenu.dropSuccessful.line1=Моды скапіяваны паспяхова +modmenu.dropSuccessful.line2=Перазапусціце гульню, каб загрузіць моды + +modmenu.modIdToolTip=Мод ID: %s +modmenu.authorPrefix=ад %s +modmenu.config=Змяніць налады +modmenu.configure=Наладзіць... +modmenu.configure.error=Немагчыма загрузіць экран налад для '%s'\nПаведаміце '%s', не Mod Menu +modmenu.website=Сайт +modmenu.issues=Пажаданні + +modmenu.credits=Аўтары: +modmenu.viewCredits=Паглядзець аўтараў +modmenu.license=Ліцэнзія: +modmenu.links=Спасылкі: +modmenu.source=Крыніца +modmenu.hasUpdate=Даступна абнаўленне: +modmenu.experimental=(Праверка абнаўленняў у Mod Menu з'яўляецца эксперыментам!) +modmenu.childHasUpdate=Абнаўленне дзіцячага мода даступна. +modmenu.updateText=v%s на %s +modmenu.downloadLink=Спампаваць + +modmenu.buymeacoffee=Buy Me a Coffee +modmenu.coindrop=Coindrop +modmenu.crowdin=Crowdin +modmenu.curseforge=CurseForge +modmenu.discord=Discord +modmenu.donate=Donate +modmenu.flattr=Flattr +modmenu.github_releases=GitHub рэлізы +modmenu.github_sponsors=GitHub спонсары +modmenu.kofi=Ko-fi +modmenu.liberapay=Liberapay +modmenu.mastodon=Mastodon +modmenu.modrinth=Modrinth +modmenu.opencollective=Open Collective +modmenu.patreon=Patreon +modmenu.paypal=PayPal +modmenu.reddit=Reddit +modmenu.twitch=Twitch +modmenu.twitter=X (былы Twitter) +modmenu.wiki=Wiki +modmenu.youtube=YouTube + +modmenu.credits.role.author=Аўтары +modmenu.credits.role.contributor=Укладальнікі +modmenu.credits.role.translator=Перакладчыкі +modmenu.credits.role.maintainer=Памочнікі +modmenu.credits.role.playtester=Тэсціроўшчыкі +modmenu.credits.role.illustrator=Ілюстратары +modmenu.credits.role.owner=Уладальнікі + +modmenu.modsFolder=Адкрыць папку модаў +modmenu.configsFolder=Адкрыць папку налад + +modmenu.nameTranslation.minecraft=Minecraft +modmenu.descriptionTranslation.minecraft=Базавая гульня. +modmenu.nameTranslation.java=Java +modmenu.descriptionTranslation.java=Бягучае асяроддзе Java. +modmenu.javaDistributionName=Бягучая: %s + +modmenu.options=Налады Mod Menu +option.modmenu.sorting=Сартыроўка +option.modmenu.sorting.ascending=A-Z +option.modmenu.sorting.descending=Z-A +option.modmenu.show_libraries=Бібліятэкі +option.modmenu.show_libraries.true=Паказаны +option.modmenu.show_libraries.false=Схаваны +option.modmenu.hide_config_buttons=Кнопка налад +option.modmenu.hide_config_buttons.true=Схавана +option.modmenu.hide_config_buttons.false=Паказана +option.modmenu.hide_badges=Іконкі модаў +option.modmenu.hide_badges.true=Схаваны +option.modmenu.hide_badges.false=Паказаны +option.modmenu.compact_list=Спіс +option.modmenu.compact_list.true=Кампактны +option.modmenu.compact_list.false=Стандартны +option.modmenu.hide_mod_links=Спасылкі модаў +option.modmenu.hide_mod_links.true=Схаваны +option.modmenu.hide_mod_links.false=Паказаны +option.modmenu.hide_mod_credits=АЎтары модаў +option.modmenu.hide_mod_credits.true=Схаваны +option.modmenu.hide_mod_credits.false=Паказаны +option.modmenu.hide_mod_license=Ліцэнзія модаў +option.modmenu.hide_mod_license.true=Схавана +option.modmenu.hide_mod_license.false=Паказана +option.modmenu.count_children=Дзіцячыя моды +option.modmenu.count_children.true=Улічваюцца +option.modmenu.count_children.false=Не улічваюцца +option.modmenu.count_libraries=Бібліятэкі +option.modmenu.count_libraries.true=Улічваюцца +option.modmenu.count_libraries.false=Не ўлічваюцца +option.modmenu.count_hidden_mods=Схаваныя моды +option.modmenu.count_hidden_mods.true=Улічваюцца +option.modmenu.count_hidden_mods.false=Не ўлічваюцца +option.modmenu.mod_count_location=Лічба модаў +option.modmenu.mod_count_location.title_screen=Вугал экрана +option.modmenu.mod_count_location.mods_button=Кнопка модаў +option.modmenu.mod_count_location.title_screen_and_mods_button=Абодва +option.modmenu.mod_count_location.none=Ніяк +option.modmenu.easter_eggs=Сакрэты +option.modmenu.easter_eggs.true=Уключаны +option.modmenu.easter_eggs.false=Адключаны +option.modmenu.mods_button_style=Кнопка модаў +option.modmenu.mods_button_style.classic=Ніжэй Realms +option.modmenu.mods_button_style.replace_realms=Замест Realms +option.modmenu.mods_button_style.shrink=Побач +option.modmenu.mods_button_style.icon=Іконкі +option.modmenu.game_menu_button_style=Меню гульні +option.modmenu.game_menu_button_style.below_bugs=Ніжэй памылак +option.modmenu.game_menu_button_style.replace_bugs=Замест памылак +option.modmenu.game_menu_button_style.icon=Іконка +option.modmenu.random_java_colors=Java колер +option.modmenu.random_java_colors.true=Ад распрацоўшчыка +option.modmenu.random_java_colors.false=Заўсёды чырвоны +option.modmenu.translate_names=Назвы +option.modmenu.translate_names.true=Лакалізаваны +option.modmenu.translate_names.false=Не лакалізаваны +option.modmenu.translate_descriptions=Апісання +option.modmenu.translate_descriptions.true=Лакалізаваныя +option.modmenu.translate_descriptions.false=Не лакалізаваныя +option.modmenu.update_checker=Праверка абнаўленняў +option.modmenu.update_checker.true=Уключана +option.modmenu.update_checker.false=Адключана +option.modmenu.button_update_badge=Індыкатар абнаўленняў +option.modmenu.button_update_badge.true=Паказаны +option.modmenu.button_update_badge.false=Схаваны" +option.modmenu.quick_configure=Хуткія налады +option.modmenu.quick_configure.true=Уключана +option.modmenu.quick_configure.false=Адключана +option.modmenu.update_channel=Канал абнаўленняў +option.modmenu.update_channel.alpha=Усё +option.modmenu.update_channel.beta=Выпуск і Бэта +option.modmenu.update_channel.release=Выпуск From 356408c6cc915093ff092a20be3b8370164deec0 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Sat, 15 Jun 2024 11:00:19 -0400 Subject: [PATCH 21/23] Skip unknown badge keys (#720) Fixes #693 - Fixed crash with unknown badges --- .../terraformersmc/modmenu/util/mod/Mod.java | 15 ++++++++++++-- .../modmenu/util/mod/fabric/FabricMod.java | 20 ++++++++++--------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java index 8fa5a4af..27e9322b 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java @@ -1,5 +1,7 @@ package com.terraformersmc.modmenu.util.mod; +import com.google.common.base.Predicates; +import com.terraformersmc.modmenu.ModMenu; import com.terraformersmc.modmenu.api.UpdateChecker; import com.terraformersmc.modmenu.api.UpdateInfo; import com.terraformersmc.modmenu.config.ModMenuConfig; @@ -168,8 +170,17 @@ public int getFillColor() { return this.fillColor; } - public static Set convert(Set badgeKeys) { - return badgeKeys.stream().map(KEY_MAP::get).collect(Collectors.toSet()); + public static Set convert(Set badgeKeys, String modId) { + return badgeKeys.stream() + .map(key -> { + if (!KEY_MAP.containsKey(key)) { + ModMenu.LOGGER.warn("Skipping unknown badge key '{}' specified by mod '{}'", key, modId); + } + + return KEY_MAP.get(key); + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); } static { diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java index aeb880a5..427fd9d3 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java @@ -54,7 +54,9 @@ public FabricMod(ModContainer modContainer, Set modpackMods) { this.container = modContainer; this.metadata = modContainer.getMetadata(); - if ("minecraft".equals(metadata.getId()) || "java".equals(metadata.getId())) { + String id = metadata.getId(); + + if ("minecraft".equals(id) || "java".equals(id)) { allowsUpdateChecks = false; } @@ -80,13 +82,13 @@ public FabricMod(ModContainer modContainer, Set modpackMods) { CustomValueUtil.getString("icon", parentObj), CustomValueUtil.getStringSet("badges", parentObj).orElse(new HashSet<>()) ); - if (parentId.orElse("").equals(this.metadata.getId())) { + if (parentId.orElse("").equals(id)) { parentId = Optional.empty(); parentData = null; throw new RuntimeException("Mod declared itself as its own parent"); } } catch (Throwable t) { - LOGGER.error("Error loading parent data from mod: " + metadata.getId(), t); + LOGGER.error("Error loading parent data from mod: " + id, t); } } } @@ -97,12 +99,12 @@ public FabricMod(ModContainer modContainer, Set modpackMods) { this.modMenuData = new ModMenuData( badgeNames, parentId, - parentData + parentData, + id ); /* Hardcode parents and badges for OSL & Fabric Loader */ - String id = metadata.getId(); - if (id.startsWith("osl-")) { + if (metadata.getId().startsWith("osl-")) { modMenuData.fillParentIfEmpty("osl"); modMenuData.badges.add(Badge.LIBRARY); } @@ -364,8 +366,8 @@ static class ModMenuData { private @Nullable final DummyParentData dummyParentData; - public ModMenuData(Set badges, Optional parent, DummyParentData dummyParentData) { - this.badges = Badge.convert(badges); + public ModMenuData(Set badges, Optional parent, DummyParentData dummyParentData, String id) { + this.badges = Badge.convert(badges, id); this.parent = parent; this.dummyParentData = dummyParentData; } @@ -412,7 +414,7 @@ public DummyParentData(String id, Optional name, Optional descri this.name = name; this.description = description; this.icon = icon; - this.badges = Badge.convert(badges); + this.badges = Badge.convert(badges, id); } public String getId() { From 1acb45d77eaedf3145f7254bd6913006e58d808a Mon Sep 17 00:00:00 2001 From: Lilly Rose Berner Date: Sat, 15 Jun 2024 17:01:51 +0200 Subject: [PATCH 22/23] Fix update checker promoting downgrades, other followup improvements (#716) - Fixed update checker promoting downgrades --- .../gui/widget/DescriptionListWidget.java | 34 +-- .../modmenu/util/UpdateCheckerThread.java | 17 -- .../util/UpdateCheckerThreadFactory.java | 16 ++ .../modmenu/util/UpdateCheckerUtil.java | 219 +++++++++++++++--- .../modmenu/util/mod/ModrinthUpdateInfo.java | 12 + .../mod/fabric/FabricLoaderUpdateChecker.java | 13 +- .../modmenu/util/mod/fabric/FabricMod.java | 1 - .../mod/quilt/QuiltLoaderUpdateChecker.java | 38 +-- .../resources/assets/modmenu/lang/en_US.lang | 16 +- 9 files changed, 267 insertions(+), 99 deletions(-) delete mode 100644 src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java create mode 100644 src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThreadFactory.java diff --git a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java index c22253ec..c2e98bee 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java @@ -36,7 +36,6 @@ public class DescriptionListWidget extends EntryListWidget { private static final String HAS_UPDATE_TEXT = I18n.translate("modmenu.hasUpdate"); private static final String EXPERIMENTAL_TEXT = Formatting.GOLD + I18n.translate("modmenu.experimental"); - private static final String MODRINTH_TEXT = I18n.translate("modmenu.modrinth"); private static final String DOWNLOAD_TEXT = "" + Formatting.BLUE + Formatting.UNDERLINE + I18n.translate("modmenu.downloadLink"); private static final String CHILD_HAS_UPDATE_TEXT = I18n.translate("modmenu.childHasUpdate"); private static final String LINKS_TEXT = I18n.translate("modmenu.links"); @@ -126,29 +125,20 @@ public void render(int mouseX, int mouseY, float delta) { this.entries.add(new DescriptionEntry((String) line, 8)); } - if (updateInfo instanceof ModrinthUpdateInfo) { - ModrinthUpdateInfo modrinthUpdateInfo = (ModrinthUpdateInfo) updateInfo; - String updateText = "" + Formatting.BLUE + Formatting.UNDERLINE + I18n.translate("modmenu.updateText", VersionUtil.stripPrefix(modrinthUpdateInfo.getVersionNumber()), MODRINTH_TEXT); - - for (Object line : textRenderer.split(updateText, wrapWidth - 16)) { - this.entries.add(new LinkEntry((String) line, modrinthUpdateInfo.getDownloadLink(), 8)); - } + String updateMessage = updateInfo.getUpdateMessage(); + String downloadLink = updateInfo.getDownloadLink(); + if (updateMessage == null) { + updateMessage = DOWNLOAD_TEXT; } else { - String updateMessage = updateInfo.getUpdateMessage(); - String downloadLink = updateInfo.getDownloadLink(); - if (updateMessage == null) { - updateMessage = DOWNLOAD_TEXT; - } else { - if (downloadLink != null) { - updateMessage = "" + Formatting.BLUE + Formatting.UNDERLINE + updateMessage; - } + if (downloadLink != null) { + updateMessage = "" + Formatting.BLUE + Formatting.UNDERLINE + updateMessage; } - for (Object line : textRenderer.split(updateMessage, wrapWidth - 16)) { - if (downloadLink != null) { - this.entries.add(new LinkEntry((String) line, downloadLink, 8)); - } else { - this.entries.add(new DescriptionEntry((String) line, 8)); - } + } + for (Object line : textRenderer.split(updateMessage, wrapWidth - 16)) { + if (downloadLink != null) { + this.entries.add(new LinkEntry((String) line, downloadLink, 8)); + } else { + this.entries.add(new DescriptionEntry((String) line, 8)); } } } diff --git a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java deleted file mode 100644 index e29cb8d6..00000000 --- a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.terraformersmc.modmenu.util; - -import com.terraformersmc.modmenu.util.mod.Mod; - -public class UpdateCheckerThread extends Thread { - - protected UpdateCheckerThread(Mod mod, Runnable runnable) { - super(runnable); - setDaemon(true); - setName(String.format("Update Checker/%s", mod.getName())); - } - - public static void run(Mod mod, Runnable runnable) { - new UpdateCheckerThread(mod, runnable).start(); - } - -} diff --git a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThreadFactory.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThreadFactory.java new file mode 100644 index 00000000..e0eed63e --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThreadFactory.java @@ -0,0 +1,16 @@ +package com.terraformersmc.modmenu.util; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +public class UpdateCheckerThreadFactory implements ThreadFactory { + static final AtomicInteger COUNT = new AtomicInteger(-1); + + @Override + public Thread newThread(@NotNull Runnable runnable) { + int index = COUNT.incrementAndGet(); + return new Thread(runnable, "ModMenu/Update Checker/" + index); + } +} diff --git a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java index 81dc4ed0..10277b36 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java +++ b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java @@ -19,11 +19,18 @@ import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.net.URI; +import java.time.Instant; +import java.time.format.DateTimeParseException; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; public class UpdateCheckerUtil { public static final Logger LOGGER = LogManager.getLogger("Mod Menu/Update Checker"); @@ -40,39 +47,90 @@ public static void checkForUpdates() { } LOGGER.info("Checking mod updates..."); - - CompletableFuture.runAsync(UpdateCheckerUtil::checkForModrinthUpdates); - checkForCustomUpdates(); + CompletableFuture.runAsync(UpdateCheckerUtil::checkForUpdates0); } - public static void checkForCustomUpdates() { + private static void checkForUpdates0() { + ExecutorService executor = Executors.newCachedThreadPool(new UpdateCheckerThreadFactory()); + List withoutUpdateChecker = new ArrayList<>(); + ModMenu.MODS.values().stream().filter(UpdateCheckerUtil::allowsUpdateChecks).forEach(mod -> { UpdateChecker updateChecker = mod.getUpdateChecker(); if (updateChecker == null) { - return; - } + withoutUpdateChecker.add(mod); // Fall back to update checking via Modrinth + } else { + executor.submit(() -> { + // We don't know which mod the thread is for yet in the thread factory + Thread.currentThread().setName("ModMenu/Update Checker/" + mod.getName()); - UpdateCheckerThread.run(mod, () -> { - UpdateInfo update = updateChecker.checkForUpdates(); + UpdateInfo update = updateChecker.checkForUpdates(); - if (update == null) { - return; - } + if (update == null) { + return; + } - mod.setUpdateInfo(update); - LOGGER.info("Update available for '{}@{}'", mod.getId(), mod.getVersion()); - }); + mod.setUpdateInfo(update); + LOGGER.info("Update available for '{}@{}'", mod.getId(), mod.getVersion()); + }); + } }); - } - public static void checkForModrinthUpdates() { if (modrinthApiV2Deprecated) { return; } - Map> modHashes = new HashMap<>(); - new ArrayList<>(ModMenu.MODS.values()).stream().filter(UpdateCheckerUtil::allowsUpdateChecks).filter(mod -> mod.getUpdateChecker() == null).forEach(mod -> { + Map> modHashes = getModHashes(withoutUpdateChecker); + + Future> currentVersionsFuture = executor.submit(() -> getCurrentVersions(modHashes.keySet())); + Future> updatedVersionsFuture = executor.submit(() -> getUpdatedVersions(modHashes.keySet())); + + Map currentVersions = null; + Map updatedVersions = null; + + try { + currentVersions = currentVersionsFuture.get(); + updatedVersions = updatedVersionsFuture.get(); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + if (currentVersions == null || updatedVersions == null) { + return; + } + + for (String hash : modHashes.keySet()) { + Instant date = currentVersions.get(hash); + VersionUpdate data = updatedVersions.get(hash); + + if (date == null || data == null) { + continue; + } + + // Current version is still the newest + if (Objects.equals(hash, data.hash)) { + continue; + } + + // Current version is newer than what's + // Available on our preferred update channel + if (date.compareTo(data.releaseDate) >= 0) { + continue; + } + + for (Mod mod : modHashes.get(hash)) { + mod.setUpdateInfo(data.asUpdateInfo()); + LOGGER.info("Update available for '{}@{}', (-> {})", mod.getId(), mod.getVersion(), data.versionNumber); + } + } + } + + private static Map> getModHashes(Collection mods) { + Map> results = new HashMap<>(); + + for (Mod mod : mods) { String modId = mod.getId(); try { @@ -80,18 +138,82 @@ public static void checkForModrinthUpdates() { if (hash != null) { LOGGER.debug("Hash for {} is {}", modId, hash); - modHashes.putIfAbsent(hash, new HashSet<>()); - modHashes.get(hash).add(mod); + results.putIfAbsent(hash, new HashSet<>()); + results.get(hash).add(mod); } } catch (IOException e) { LOGGER.error("Error getting mod hash for mod {}: ", modId, e); } - }); + } - List loaders = ModMenu.runningQuilt ? Arrays.asList("fabric", "quilt") : Arrays.asList("fabric"); + return results; + } + + /** + * @return a map of file hash to its release date on Modrinth. + */ + private static @Nullable Map getCurrentVersions(Collection modHashes) { + String body = ModMenu.GSON_MINIFIED.toJson(new CurrentVersionsFromHashes(modHashes)); + + try { + RequestBuilder request = RequestBuilder.post() + .setEntity(new StringEntity(body)) + .addHeader("Content-Type", "application/json") + .setUri(URI.create("https://api.modrinth.com/v2/version_files")); + + HttpResponse response = HttpUtil.request(request); + int status = response.getStatusLine().getStatusCode(); + + if (status == 410) { + modrinthApiV2Deprecated = true; + LOGGER.warn("Modrinth API v2 is deprecated, unable to check for mod updates."); + } else if (status == 200) { + Map results = new HashMap<>(); + JsonObject data = new JsonParser().parse(EntityUtils.toString(response.getEntity())).getAsJsonObject(); + + data.entrySet().forEach((Map.Entry entry) -> { + Instant date; + JsonObject version = entry.getValue().getAsJsonObject(); + + try { + date = Instant.parse(version.get("date_published").getAsString()); + } catch (DateTimeParseException e) { + return; + } + + results.put(entry.getKey(), date); + }); + + return results; + } + } catch (IOException e) { + LOGGER.error("Error checking for versions: ", e); + } + return null; + } + + public static class CurrentVersionsFromHashes { + public Collection hashes; + public String algorithm = "sha512"; + + public CurrentVersionsFromHashes(Collection hashes) { + this.hashes = hashes; + } + } + + private static UpdateChannel getUpdateChannel(String versionType) { + try { + return UpdateChannel.valueOf(versionType.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException | NullPointerException e) { + return UpdateChannel.RELEASE; + } + } + + private static @Nullable Map getUpdatedVersions(Collection modHashes) { String mcVer = FabricLoader.getInstance().getModContainer("minecraft").get() .getMetadata().getVersion().getFriendlyString(); + List loaders = ModMenu.runningQuilt ? Arrays.asList("fabric", "quilt") : Arrays.asList("fabric"); List updateChannels; UpdateChannel preferredChannel = UpdateChannel.getUserPreference(); @@ -104,7 +226,7 @@ public static void checkForModrinthUpdates() { updateChannels = Arrays.asList(UpdateChannel.ALPHA, UpdateChannel.BETA, UpdateChannel.RELEASE); } - String body = ModMenu.GSON_MINIFIED.toJson(new LatestVersionsFromHashesBody(modHashes.keySet(), loaders, mcVer, updateChannels)); + String body = ModMenu.GSON_MINIFIED.toJson(new LatestVersionsFromHashesBody(modHashes, loaders, mcVer, updateChannels)); LOGGER.debug("Body: " + body); @@ -114,15 +236,16 @@ public static void checkForModrinthUpdates() { .addHeader("Content-Type", "application/json") .setUri(URI.create("https://api.modrinth.com/v2/version_files/update")); - HttpResponse latestVersionsResponse = HttpUtil.request(latestVersionsRequest); + HttpResponse response = HttpUtil.request(latestVersionsRequest); - int status = latestVersionsResponse.getStatusLine().getStatusCode(); + int status = response.getStatusLine().getStatusCode(); LOGGER.debug("Status: " + status); if (status == 410) { modrinthApiV2Deprecated = true; LOGGER.warn("Modrinth API v2 is deprecated, unable to check for mod updates."); } else if (status == 200) { - JsonObject responseObject = new JsonParser().parse(EntityUtils.toString(latestVersionsResponse.getEntity())).getAsJsonObject(); + Map results = new HashMap<>(); + JsonObject responseObject = new JsonParser().parse(EntityUtils.toString(response.getEntity())).getAsJsonObject(); LOGGER.debug(String.valueOf(responseObject)); responseObject.entrySet().forEach(entry -> { String lookupHash = entry.getKey(); @@ -140,28 +263,48 @@ public static void checkForModrinthUpdates() { return; } + Instant date; + + try { + date = Instant.parse(versionObj.get("date_published").getAsString()); + } catch (DateTimeParseException e) { + return; + } + UpdateChannel updateChannel = UpdateCheckerUtil.getUpdateChannel(versionType); String versionHash = primaryFile.get().getAsJsonObject().get("hashes").getAsJsonObject().get("sha512").getAsString(); - if (!Objects.equals(versionHash, lookupHash)) { - // hashes different, there's an update. - modHashes.get(lookupHash).forEach(mod -> { - LOGGER.info("Update available for '{}@{}', (-> {})", mod.getId(), mod.getVersion(), versionNumber); - mod.setUpdateInfo(new ModrinthUpdateInfo(projectId, versionId, versionNumber, updateChannel)); - }); - } + results.put(lookupHash, new VersionUpdate(projectId, versionId, versionNumber, date, updateChannel, versionHash)); }); + + return results; } } catch (IOException e) { LOGGER.error("Error checking for updates: ", e); } + + return null; } - private static UpdateChannel getUpdateChannel(String versionType) { - try { - return UpdateChannel.valueOf(versionType.toUpperCase(Locale.ROOT)); - } catch (IllegalArgumentException | NullPointerException e) { - return UpdateChannel.RELEASE; + private static class VersionUpdate { + String projectId; + String versionId; + String versionNumber; + Instant releaseDate; + UpdateChannel updateChannel; + String hash; + + public VersionUpdate(String projectId, String versionId, String versionNumber, Instant releaseDate, UpdateChannel updateChannel, String has) { + this.projectId = projectId; + this.versionId = versionId; + this.versionNumber = versionNumber; + this.releaseDate = releaseDate; + this.updateChannel = updateChannel; + this.hash = has; + } + + private UpdateInfo asUpdateInfo() { + return new ModrinthUpdateInfo(this.projectId, this.versionId, this.versionNumber, this.updateChannel); } } diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java b/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java index 254aa4f6..748b76a1 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java @@ -1,7 +1,12 @@ package com.terraformersmc.modmenu.util.mod; +import org.jetbrains.annotations.Nullable; + import com.terraformersmc.modmenu.api.UpdateChannel; import com.terraformersmc.modmenu.api.UpdateInfo; +import com.terraformersmc.modmenu.util.VersionUtil; + +import net.minecraft.client.resource.language.I18n; public class ModrinthUpdateInfo implements UpdateInfo { protected final String projectId; @@ -9,6 +14,8 @@ public class ModrinthUpdateInfo implements UpdateInfo { protected final String versionNumber; protected final UpdateChannel updateChannel; + private static final String MODRINTH_TEXT = I18n.translate("modmenu.modrinth"); + public ModrinthUpdateInfo(String projectId, String versionId, String versionNumber, UpdateChannel updateChannel) { this.projectId = projectId; this.versionId = versionId; @@ -21,6 +28,11 @@ public boolean isUpdateAvailable() { return true; } + @Override + public @Nullable String getUpdateMessage() { + return I18n.translate("modmenu.updateText", VersionUtil.stripPrefix(this.versionNumber), MODRINTH_TEXT); + } + @Override public String getDownloadLink() { return String.format("https://modrinth.com/project/%s/version/%s", projectId, versionId); diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java index 726b842f..bf67d19b 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java @@ -10,6 +10,7 @@ import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -25,6 +26,7 @@ import net.fabricmc.loader.api.SemanticVersion; import net.fabricmc.loader.api.Version; import net.fabricmc.loader.api.VersionParsingException; +import net.minecraft.client.resource.language.I18n; public class FabricLoaderUpdateChecker implements UpdateChecker { public static final Logger LOGGER = LogManager.getLogger("Mod Menu/Fabric Update Checker"); @@ -116,7 +118,7 @@ private static UpdateInfo checkForUpdates0() throws IOException, InterruptedExce } LOGGER.debug("Fabric Loader has a matching update available!"); - return new FabricLoaderUpdateInfo(stableVersion); + return new FabricLoaderUpdateInfo(match.getFriendlyString(), stableVersion); } private static boolean isNewer(Version self, Version other) { @@ -128,9 +130,11 @@ private static Version getCurrentVersion() { } private static class FabricLoaderUpdateInfo implements UpdateInfo { + private final String version; private final boolean isStable; - private FabricLoaderUpdateInfo(boolean isStable) { + private FabricLoaderUpdateInfo(String version, boolean isStable) { + this.version = version; this.isStable = isStable; } @@ -139,6 +143,11 @@ public boolean isUpdateAvailable() { return true; } + @Override + public @Nullable String getUpdateMessage() { + return I18n.translate("modmenu.install_version", this.version); + } + @Override public String getDownloadLink() { return "https://fabricmc.net/use/installer"; diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java index 427fd9d3..98ba43cc 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java @@ -20,7 +20,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.lwjgl.system.CallbackI.V; import java.awt.image.BufferedImage; import java.io.File; diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java index 35dee024..b3102b43 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java @@ -10,6 +10,7 @@ import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import org.quiltmc.loader.api.QuiltLoader; import org.quiltmc.loader.api.Version; import org.quiltmc.loader.api.VersionFormatException; @@ -23,6 +24,8 @@ import com.terraformersmc.modmenu.util.HttpUtil; import com.terraformersmc.modmenu.util.JsonUtil; +import net.minecraft.client.resource.language.I18n; + public class QuiltLoaderUpdateChecker implements UpdateChecker { public static final Logger LOGGER = LogManager.getLogger("Mod Menu/Quilt Update Checker"); private static final URI LOADER_VERSIONS = URI.create("https://meta.quiltmc.org/v3/versions/loader"); @@ -110,19 +113,7 @@ private static UpdateInfo checkForUpdates0() throws IOException, InterruptedExce } LOGGER.debug("Quilt Loader has a matching update available!"); - - UpdateChannel updateChannel; - String preRelease = match.preRelease(); - - if (preRelease.isEmpty()) { - updateChannel = UpdateChannel.RELEASE; - } else if (isStableOrBeta(preRelease)) { - updateChannel = UpdateChannel.BETA; - } else { - updateChannel = UpdateChannel.ALPHA; - } - - return new QuiltLoaderUpdateInfo(updateChannel); + return new QuiltLoaderUpdateInfo(match); } private static boolean isNewer(Version.Semantic self, Version.Semantic other) { @@ -138,10 +129,10 @@ private static boolean isStableOrBeta(String preRelease) { } private static class QuiltLoaderUpdateInfo implements UpdateInfo { - private final UpdateChannel updateChannel; + private final Version.Semantic version; - private QuiltLoaderUpdateInfo(UpdateChannel updateChannel) { - this.updateChannel = updateChannel; + private QuiltLoaderUpdateInfo(Version.Semantic version) { + this.version = version; } @Override @@ -149,6 +140,11 @@ public boolean isUpdateAvailable() { return true; } + @Override + public @Nullable String getUpdateMessage() { + return I18n.translate("modmenu.install_version", this.version.raw()); + } + @Override public String getDownloadLink() { return "https://quiltmc.org/en/install/client"; @@ -156,7 +152,15 @@ public String getDownloadLink() { @Override public UpdateChannel getUpdateChannel() { - return this.updateChannel; + String preRelease = this.version.preRelease(); + + if (preRelease.isEmpty()) { + return UpdateChannel.RELEASE; + } else if (isStableOrBeta(preRelease)) { + return UpdateChannel.BETA; + } else { + return UpdateChannel.ALPHA; + } } } } diff --git a/src/main/resources/assets/modmenu/lang/en_US.lang b/src/main/resources/assets/modmenu/lang/en_US.lang index 60ccd58b..e88bad19 100644 --- a/src/main/resources/assets/modmenu/lang/en_US.lang +++ b/src/main/resources/assets/modmenu/lang/en_US.lang @@ -3,6 +3,7 @@ key.modmenu.open_menu=Open Mod Menu modmenu.title=Mods modmenu.nameTranslation.modmenu=Mod Menu modmenu.descriptionTranslation.modmenu=Adds a mod menu to view the list of mods you have installed. + modmenu.loaded=(%s Loaded) modmenu.loaded.short=(%s) modmenu.loaded.69.secret=(%s Loaded...nice) @@ -11,6 +12,7 @@ modmenu.mods.n= (%s Mods) modmenu.mods.1= (%s Mod) modmenu.mods.69.secret= (%s Mods...nice) modmenu.mods.420.secret= (%s Mods...blaze it) + modmenu.search=Search for mods modmenu.searchTerms.library=api library modmenu.searchTerms.patchwork=patchwork forge fml @@ -19,6 +21,7 @@ modmenu.searchTerms.deprecated=deprecated outdated old modmenu.searchTerms.clientside=clientside gameside modmenu.searchTerms.configurable=configurations configs configures configurable options settings modmenu.searchTerms.hasUpdate=updates version + modmenu.toggleFilterOptions=Toggle Filter Options modmenu.showingMods.n=Showing %s mods modmenu.showingMods.1=Showing %s mod @@ -28,25 +31,28 @@ modmenu.showingModsLibraries.n.n=Showing %s mods and %s libraries modmenu.showingModsLibraries.n.1=Showing %s mods and %s library modmenu.showingModsLibraries.1.n=Showing %s mod and %s libraries modmenu.showingModsLibraries.1.1=Showing %s mod and %s library + modmenu.badge.library=Library modmenu.badge.clientsideOnly=Client modmenu.badge.deprecated=Deprecated modmenu.badge.forge=Forge modmenu.badge.modpack=Modpack modmenu.badge.minecraft=Minecraft + modmenu.dropInfo.line1=Drag and drop files into modmenu.dropInfo.line2=this window to add mods modmenu.dropConfirm=Do you want to copy the following mods into the mods directory? modmenu.dropSuccessful.line1=Successfully copied mods modmenu.dropSuccessful.line2=Restart game to load mods + modmenu.modIdToolTip=Mod ID: %s modmenu.authorPrefix=By %s modmenu.config=Edit Config modmenu.configure=Configure... -modmenu.configure.error=Failed to load config screen for '%s' -Report to '%s', not Mod Menu +modmenu.configure.error=Failed to load config screen for '%s'\nReport to '%s', not Mod Menu modmenu.website=Website modmenu.issues=Issues + modmenu.credits=Credits: modmenu.viewCredits=View Credits modmenu.license=License: @@ -56,7 +62,9 @@ modmenu.hasUpdate=An update is available: modmenu.experimental=(Mod Menu update checker is experimental!) modmenu.childHasUpdate=A child of this mod has an update available. modmenu.updateText=v%s on %s +modmenu.install_version=Install version %s modmenu.downloadLink=Download + modmenu.buymeacoffee=Buy Me a Coffee modmenu.coindrop=Coindrop modmenu.crowdin=Crowdin @@ -78,6 +86,7 @@ modmenu.twitch=Twitch modmenu.twitter=X (Twitter) modmenu.wiki=Wiki modmenu.youtube=YouTube + modmenu.credits.role.author=Authors modmenu.credits.role.contributor=Contributors modmenu.credits.role.translator=Translators @@ -85,13 +94,16 @@ modmenu.credits.role.maintainer=Maintainers modmenu.credits.role.playtester=Playtesters modmenu.credits.role.illustrator=Illustrators modmenu.credits.role.owner=Owners + modmenu.modsFolder=Open Mods Folder modmenu.configsFolder=Open Configs Folder + modmenu.nameTranslation.minecraft=Minecraft modmenu.descriptionTranslation.minecraft=The base game. modmenu.nameTranslation.java=Java modmenu.descriptionTranslation.java=The Java runtime environment. modmenu.javaDistributionName=Running: %s + modmenu.options=Mod Menu Options option.value_label=%s: %s option.modmenu.sorting=Sort From 2bda2312d46ee90e7baa01a2897235786de43649 Mon Sep 17 00:00:00 2001 From: LostLuma Date: Sun, 16 Jun 2024 17:31:00 +0200 Subject: [PATCH 23/23] Change loader update checker download link to Ornithe website --- .../modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java | 2 +- .../modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java index bf67d19b..848c6289 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricLoaderUpdateChecker.java @@ -150,7 +150,7 @@ public boolean isUpdateAvailable() { @Override public String getDownloadLink() { - return "https://fabricmc.net/use/installer"; + return "https://ornithemc.net"; } @Override diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java index b3102b43..5d69e895 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltLoaderUpdateChecker.java @@ -147,7 +147,7 @@ public boolean isUpdateAvailable() { @Override public String getDownloadLink() { - return "https://quiltmc.org/en/install/client"; + return "https://ornithemc.net"; } @Override