@@ -0,0 +1,1842 @@
+package com.github.sormuras.bach.internal;%0A%0Aimport java.net.URI;%0Aimport java.net.URLDecoder;%0Aimport java.nio.charset.StandardCharsets;%0Aimport java.util.Optional;%0Aimport java.util.regex.Pattern;%0A%0A/** GitHub-related helper. */%0Apublic class GitHub %7B%0A%0A  private final String base;%0A  private final Pattern latestCommitHashPattern;%0A  private final Pattern latestReleaseTagPattern;%0A%0A  public GitHub(String user, String repo) %7B%0A    this.base = %22https://github.com/%22 + user + %22/%22 + repo;%0A    this.latestCommitHashPattern = Pattern.compile(%22%5C%22%22 + base + %22/tree/(.%7B7%7D).*%5C%22%22);%0A    this.latestReleaseTagPattern = Pattern.compile(%22%5C%22/%22 + user + %22/%22 + repo + %22/tree/(.+?)%5C%22%22);%0A  %7D%0A%0A  public Optional%3CString%3E findLatestCommitHash() %7B%0A    return find(browse(base + %22/tree/HEAD%22), latestCommitHashPattern);%0A  %7D%0A%0A  public Optional%3CString%3E findLatestReleaseTag() %7B%0A    return find(browse(base + %22/releases/latest%22), latestReleaseTagPattern);%0A  %7D%0A%0A  public Optional%3CString%3E findReleasedModule(String module, String version) %7B%0A    var file = module + '@' + version + %22.jar%22;%0A    var path = String.join(%22/%22, %22releases/download%22, version, file);%0A    var page = browse(base + %22/releases/tag/%22 + version);%0A    return page.contains(file) ? Optional.of(base + '/' + path) : Optional.empty();%0A  %7D%0A%0A  private static String browse(String uri) %7B%0A    try (var stream = URI.create(uri).toURL().openStream()) %7B%0A      return new String(stream.readAllBytes(), StandardCharsets.UTF_8);%0A    %7D catch (Exception e) %7B%0A      return e.toString();%0A    %7D%0A  %7D%0A%0A  private static Optional%3CString%3E find(String string, Pattern pattern) %7B%0A    var matcher = pattern.matcher(string);%0A    if (matcher.find()) %7B%0A      var encoded = matcher.group(1);%0A      var decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8);%0A      return Optional.of(decoded);%0A    %7D%0A    return Optional.empty();%0A  %7D%0A%7D%0A

Introduce GitHub-related helper