Skip to content

Comments

feat(proxy): implement proxy configuration and detection for microbot#1455

Merged
chsami merged 3 commits intodevelopmentfrom
chsami/proxy_splashsscreen_rework
Aug 31, 2025
Merged

feat(proxy): implement proxy configuration and detection for microbot#1455
chsami merged 3 commits intodevelopmentfrom
chsami/proxy_splashsscreen_rework

Conversation

@chsami
Copy link
Owner

@chsami chsami commented Aug 31, 2025

Summary by CodeRabbit

  • New Features

    • Startup validates proxies, tests/detects external IP, displays it in the UI, and aborts on invalid proxy.
    • Redesigned splash screen with rounded layout, close button, enhanced progress, and rotating “Did you know?” facts fetched periodically.
    • Random fact fetcher added for splash screen.
  • Bug Fixes

    • Update notice only shown when a newer Microbot version is available.
    • Plugin Hub hides disabled plugins and shows accurate install status and counts.
  • Style

    • Window title now includes full proxy status information.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 31, 2025

Walkthrough

Centralizes SOCKS5 proxy setup and runtime proxy verification; redesigns the SplashScreen with rounded UI and periodic random-fact polling; improves Microbot version-title updates; refactors Microbot plugin list building; adds ProxyChecker, ProxyConfiguration, RandomFactClient, and minor logging/import adjustments.

Changes

Cohort / File(s) Summary
Proxy setup & detection
runelite-client/src/main/java/net/runelite/client/RuneLite.java, runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java, runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java, runelite-client/src/main/java/net/runelite/client/proxy/ProxyChecker.java
Replace inlined proxy parsing with ProxyConfiguration.setupProxy(options, proxyInfo) that enforces SOCKS5 and installs JVM Authenticator when credentials present; add ProxyChecker.getDetectedIp(OkHttpClient) and perform runtime proxy test during startup, updating ClientUI.proxyMessage on success or calling Microbot.showMessage and exiting on failure.
Splash screen redesign & facts
runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java, runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java
Replace splash UI with rounded, undecorated window, custom close control and styled progress; add scheduled fact polling via new RandomFactClient (sync/async), public getFact/setFact APIs, property-change updates, and scheduler cancellation on stop.
Microbot plugin hub list building
runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java
Refactor reloadPluginList to exclude disabled manifests, pre-index manifests and loaded plugins by lowercased names, compute counts and installed status from pre-indexed maps, and construct PluginItem list from those collections; formatting/import changes.
Version-aware title updates
runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotVersionChecker.java
Add numeric dot-separated version comparison and new appendToTitle(remote, local) to update frame title only when remote > local; preserve EDT-safe UI updates and logging.
Client UI title change & imports
runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
Always append the full proxyMessage to the window title (changed concatenation) and consolidate import statements to broader/wildcard imports.
Microbot logging annotation
runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotApi.java
Add Lombok @Slf4j to enable a generated log field; no behavioral changes to public APIs.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant App as RuneLite
  participant Splash as SplashScreen
  participant ProxyCfg as ProxyConfiguration
  participant HTTP as OkHttpClient
  participant ProxyChk as ProxyChecker
  participant UI as ClientUI
  participant MB as Microbot

  User->>App: Launch with options
  App->>Splash: stage("Setting up proxy")
  App->>ProxyCfg: setupProxy(options, proxyInfo)
  App->>Splash: stage("Testing proxy address...")
  App->>HTTP: init OkHttpClient
  App->>ProxyChk: getDetectedIp(HTTP)
  alt IP detected
    App->>UI: set proxyMessage("Proxy enabled: <ip>")
    UI-->>User: Title shows proxy info
  else detection failed
    App->>MB: showMessage("Failed to detect proxy")
    App->>App: System.exit(1)
  end
Loading
sequenceDiagram
  autonumber
  participant App
  participant Splash as SplashScreen
  participant Sched as ScheduledExecutor
  participant Facts as RandomFactClient
  participant UI as Splash UI

  App->>Splash: init()
  Splash->>Sched: scheduleAtFixedRate(fetchFact, 20s)
  loop every 20s
    Sched->>Facts: getRandomFactAsync(callback)
    Facts-->>Splash: callback(fact) [on EDT]
    Splash->>Splash: setFact(fact)
    Splash->>UI: PropertyChange("fact")
    UI-->>User: Updated fact displayed
  end
  App->>Splash: stop()
  Splash->>Sched: cancel()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

* @param remoteVersion
* @param localVersion
*/
private void appendToTitle(String remoteVersion, String localVersion) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has already been resolved here: 943ba82

I had placed some shared methods I was using in the Microbot Plugin Manager into Rs2UiHelper

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotApi.java (1)

48-56: Handle HTTP errors and null bodies in microbotOpen().

If the server returns a non-2xx or an empty body, this will either parse null or throw later. Add isSuccessful() check and validate body != null. Also prefer try-with-resources on ResponseBody to be explicit.

Apply this diff:

-    public UUID microbotOpen() throws IOException {
-        try (Response response = client.newCall(new Request.Builder().url(microbotApiUrl + "/session").build()).execute()) {
-            ResponseBody body = response.body();
-
-            InputStream in = body.byteStream();
-            return gson.fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), UUID.class);
+    public UUID microbotOpen() throws IOException {
+        try (Response response = client.newCall(new Request.Builder()
+            .url(microbotApiUrl + "/session")
+            .build()).execute()) {
+            if (!response.isSuccessful()) {
+                throw new IOException("Failed to open session: HTTP " + response.code());
+            }
+            try (ResponseBody body = response.body()) {
+                if (body == null) {
+                    throw new IOException("Empty response body");
+                }
+                try (InputStream in = body.byteStream();
+                     InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
+                    return gson.fromJson(reader, UUID.class);
+                }
+            }
         } catch (JsonParseException | IllegalArgumentException ex) // UUID.fromString can throw IllegalArgumentException
         {
             throw new IOException(ex);
         }
     }
runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java (1)

631-653: Loaded-plugin mapping likely broken (internalName vs class simple name).

You key manifests by internalName but key loaded plugins by getClass().getSimpleName(). These rarely match (e.g., internalName oneclickbanker vs class OneClickBankerPlugin), so group will be empty, hiding Configure and misreporting install state.

Map both sides using a common normalized key derived from the plugin’s annotated name or another reliable identifier. Example fix:

-        // Index loaded plugins by simple name (lowercased) - all instances for that name
-        Map<String, Collection<Plugin>> pluginsByName = loadedPlugins.stream()
-                .collect(Collectors.groupingBy(
-                        p -> p.getClass().getSimpleName().toLowerCase(Locale.ROOT),
-                        LinkedHashMap::new,
-                        Collectors.toCollection(LinkedHashSet::new)
-                ));
+        // Normalize helper: lower-case, strip non-alphanumerics
+        Function<String, String> keyify = s -> s == null ? "" : s.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", "");
+
+        // Index loaded plugins by a normalized key from @PluginDescriptor.name (fallback to simple class name)
+        Map<String, Collection<Plugin>> pluginsByKey = loadedPlugins.stream()
+                .collect(Collectors.groupingBy(p -> {
+                    PluginDescriptor d = p.getClass().getAnnotation(PluginDescriptor.class);
+                    String name = (d != null ? d.name() : p.getClass().getSimpleName());
+                    return keyify.apply(name);
+                }, LinkedHashMap::new, Collectors.toCollection(LinkedHashSet::new)));
@@
-        plugins = manifestByName.entrySet().stream()
+        plugins = manifestByName.entrySet().stream()
                 .map(e -> {
                     String key = e.getKey();
                     MicrobotPluginManifest m = e.getValue();
-                    String simpleName = m.getInternalName();
-
-                    Collection<Plugin> group = pluginsByName.getOrDefault(key, Collections.emptySet());
-                    int count = pluginCounts.getOrDefault(simpleName, -1);
-                    boolean isInstalled = installed.stream().anyMatch(im -> im.getInternalName().equalsIgnoreCase(simpleName));
+                    String internal = m.getInternalName();
+                    Collection<Plugin> group = pluginsByKey.getOrDefault(key, Collections.emptySet());
+                    int count = pluginCounts.getOrDefault(internal, -1);
+                    boolean isInstalled = installed.stream().anyMatch(im -> im.getInternalName().equalsIgnoreCase(internal));
 
                     return new PluginItem(m, group, count, isInstalled);
                 })
                 .collect(Collectors.toList());

This keeps sorting/counts intact and restores proper Configure wiring.

🧹 Nitpick comments (13)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotApi.java (2)

5-5: Remove unused Lombok logger or add logs.

You added @Slf4j but there are no logging calls in this class. Either remove the annotation to avoid bytecode/field clutter or add meaningful logs (e.g., request failures).


95-102: Close response with try-with-resources and check status in microbotDelete().

The call ignores HTTP status and manually closes the response. Use try-with-resources and validate success.

-    public void microbotDelete(UUID uuid) throws IOException {
-        Request request = new Request.Builder()
-                .delete()
-                .url(microbotApiUrl + "/session?sessionId=" + uuid)
-                .build();
-
-        client.newCall(request).execute().close();
-    }
+    public void microbotDelete(UUID uuid) throws IOException {
+        Request request = new Request.Builder()
+            .delete()
+            .url(microbotApiUrl + "/session?sessionId=" + uuid)
+            .build();
+        try (Response response = client.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                throw new IOException("Failed to delete session: HTTP " + response.code());
+            }
+        }
+    }
runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java (2)

169-170: Proxy message may be stale in window title.

this.title is computed once using the current static proxyMessage. If proxyMessage is set after ClientUI construction (likely), the title won’t reflect it. Ensure ClientUI.proxyMessage is set before constructing ClientUI or update the frame title after proxy detection.

Minimal approach (call after setting ClientUI.proxyMessage in RuneLite/RuneLiteDebug):

if (ClientUI.getFrame() != null) {
    ClientUI.getFrame().setTitle(ClientUI.getFrame().getTitle() + ClientUI.proxyMessage);
}

If you prefer a robust solution, I can provide a small refactor to keep a baseTitle and recompute the title whenever proxyMessage changes.


169-169: Avoid trailing space when no proxy message.

Concatenate proxyMessage only when non-empty to avoid a stray space in the title.

- this.title = title + (safeMode ? " (safe mode)" : " V" + version) + " " + proxyMessage;
+ this.title = title + (safeMode ? " (safe mode)" : " V" + version) + (proxyMessage.isEmpty() ? "" : " " + proxyMessage);
runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotVersionChecker.java (2)

3-9: Deduplicate imports.

There are repeated imports (@Slf4j, ClientUI, RuneLiteProperties, Singleton, SwingUtilities). Clean these up to reduce noise.

Also applies to: 18-24


155-175: Remove unused isLocalVersionLower() after switching to Rs2UiHelper.compareVersions.

-    private boolean isLocalVersionLower(String localVersion, String remoteVersion) {
-        String[] local = localVersion.split("\\.");
-        String[] remote = remoteVersion.split("\\.");
-        int length = Math.min(local.length, remote.length);
-        for (int i = 0; i < length; i++) {
-            int localPart = Integer.parseInt(local[i]);
-            int remotePart = Integer.parseInt(remote[i]);
-            if (localPart != remotePart) {
-                return localPart < remotePart;
-            }
-        }
-        return local.length < remote.length;
-    }
+    // removed; use Rs2UiHelper.compareVersions(local, remote) instead
runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java (1)

91-99: Remove unused hasIcon() or use it to skip queueing blank icons.

hasIcon() is unused. Either remove it or use it in paint() to avoid queueing when iconUrl is blank.

-        private boolean hasIcon() {
-            return !iconUrl.isBlank();
-        }
+        private boolean hasIcon() {
+            return iconUrl != null && !iconUrl.isBlank();
+        }

And:

-            if (!loadingStarted) {
+            if (!loadingStarted && hasIcon()) {
                 loadingStarted = true;
                 ...
             }
runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java (4)

62-65: Remove unused escape() helper.

escape(String) is unused after switching to JTextArea.

-    static String escape(String s) {
-        return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
-    }

103-111: Splash close behavior: consider graceful shutdown instead of System.exit(0).

The window’s close button is EXIT_ON_CLOSE and the custom “×” triggers System.exit(0). This can abruptly terminate startup tasks.

Consider calling SplashScreen.stop() and letting the main app decide shutdown, or prompt the user.

Also applies to: 72-101


281-286: Duplicate progress maximum assignment.

progress.setMaximum(1000) is set both in the constructor and in actionPerformed. Keep it in one place.

-        progress.setMaximum(1000);

22-25: Prefer single, shared ScheduledExecutorService (project convention).

Per prior learnings, the project uses a shared single-thread scheduler. Consider reusing it (DI) instead of creating a new pool here.

Also applies to: 60-61

runelite-client/src/main/java/net/runelite/client/RuneLite.java (2)

190-191: CLI help: clarify encoding and SOCKS5H hint.

Consider noting “URL‑encode special characters in user/pass” and mention socks5h for remote DNS (avoids DNS leaks).

-        final ArgumentAcceptingOptionSpec<String> proxyInfo = parser.accepts("proxy", "Use a specified proxy. Format: scheme://user:pass@host:port")
+        final ArgumentAcceptingOptionSpec<String> proxyInfo = parser.accepts(
+            "proxy",
+            "Use a specified proxy. Format: scheme://user:pass@host:port (URL‑encode special characters). For SOCKS with remote DNS, use socks5h."
+        )

Also mirror this description in RuneLiteDebug for consistency.


267-277: On proxy test failure, stop Splash and harden IP check.

Minor UX and robustness: stop the splash before exiting and treat null/blank the same; also tighten the message.

-        SplashScreen.stage(0, "Setting up proxy", "Testing proxy address...");
+        SplashScreen.stage(0, "Setting up proxy", "Testing proxy address...");
         if (options.has(proxyInfo)) {
-            String ip = ProxyChecker.getDetectedIp(okHttpClient);
-            if (ip.isEmpty()) {
-                Microbot.showMessage("Failed to detect external IP address, check your proxy settings. \n\n Make sure to use the format scheme://user:pass@host:port");
-                System.exit(1);
-            }
-
-            ClientUI.proxyMessage = " - Proxy enabled (detected IP " + ip + ")";
+            String ip = ProxyChecker.getDetectedIp(okHttpClient);
+            if (ip == null || ip.isBlank()) {
+                Microbot.showMessage("Failed to detect external IP address. Check your proxy settings.\n\nExpected format: scheme://user:pass@host:port (URL‑encode special characters).");
+                SplashScreen.stop();
+                System.exit(1);
+            }
+            log.info("Proxy enabled; detected external IP {}", ip);
+            ClientUI.proxyMessage = " - Proxy enabled (detected IP " + ip + ")";
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 37b8fc8 and d0af195.

📒 Files selected for processing (7)
  • runelite-client/src/main/java/net/runelite/client/RuneLite.java (4 hunks)
  • runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotApi.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotVersionChecker.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-23T16:17:37.615Z
Learnt from: g-mason0
PR: chsami/Microbot#1423
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java:366-389
Timestamp: 2025-08-23T16:17:37.615Z
Learning: The ScheduledExecutorService injected into MicrobotPluginManager (and other RuneLite classes) is configured as a single-threaded executor via Executors.newSingleThreadScheduledExecutor() at RuneLiteModule.java line 130, wrapped in ExecutorServiceExceptionLogger. This ensures all async tasks execute sequentially, preventing race conditions.

Applied to files:

  • runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
🧬 Code graph analysis (2)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotApi.java (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotVersionChecker.java (1)
  • Slf4j (25-200)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotVersionChecker.java (1)
runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java (1)
  • RuneLiteProperties (35-162)
🪛 GitHub Actions: CI
runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java

[error] 48-48: package net.runelite.client.proxy does not exist

runelite-client/src/main/java/net/runelite/client/RuneLite.java

[error] 48-48: package net.runelite.client.proxy does not exist


[error] 49-49: package net.runelite.client.proxy does not exist

runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java

[error] 4-4: Cannot find symbol: RandomFactClient in package net.runelite.client.plugins.microbot

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (1)
runelite-client/src/main/java/net/runelite/client/RuneLite.java (1)

252-253: Good centralization of proxy setup.

Calling ProxyConfiguration.setupProxy(...) early is the right place so the subsequent OkHttp client inherits settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (9)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java (6)

30-39: Specify Accept and sanitize payload; avoid trailing whitespace.

Add an Accept header and trim the body to keep UI clean.

-            Request request = new Request.Builder()
-                    .url(MICROBOT_API_URL + "/fact/random")
-                    .build();
+            Request request = new Request.Builder()
+                    .url(MICROBOT_API_URL + "/fact/random")
+                    .header("Accept", "text/plain")
+                    .build();
...
-                return response.body().string();
+                return response.body().string().trim();

35-37: Unify error contract and add logging (avoid returning null).

Currently non-200 returns a string message while exceptions return null. This inconsistency complicates the UI path and risks NPEs. Always return the same fallback string and log errors.

-                if (!response.isSuccessful() || response.body() == null) {
-                    return "Failed to fetch random fact";
-                }
+                if (!response.isSuccessful() || response.body() == null) {
+                    log.warn("Random fact endpoint failed with HTTP {}", response.code());
+                    return "Failed to fetch random fact";
+                }
...
-        } catch (Exception e) {
-            return null;
+        } catch (Exception e) {
+            log.error("Error fetching random fact", e);
+            return "Failed to fetch random fact";
         }

Also applies to: 40-42


57-60: Guard callback from nulls for safer UI updates.

Ensure the UI always receives a non-null string to render.

-            SwingUtilities.invokeLater(() -> callback.accept(fact));
+            SwingUtilities.invokeLater(() -> callback.accept(
+                fact != null ? fact : "Failed to fetch random fact"
+            ));

49-56: Prefer a small dedicated executor over the common pool.

Using the common ForkJoinPool for blocking I/O can starve other tasks. A single-daemon thread is enough for a 20s poll.

Example (outside this range):

private static final java.util.concurrent.ExecutorService FACT_EXECUTOR =
    java.util.concurrent.Executors.newSingleThreadExecutor(r -> {
        Thread t = new Thread(r, "random-fact");
        t.setDaemon(true);
        return t;
    });

// then:
CompletableFuture.supplyAsync(RandomFactClient::getRandomFact, FACT_EXECUTOR)

16-18: Make utility class non-instantiable.

Minor hygiene: mark final and add a private constructor.

-@Slf4j
-public class RandomFactClient {
+@Slf4j
+public final class RandomFactClient {
+    private RandomFactClient() {}

24-27: Clarify return semantics in Javadoc.

Document that the method never throws and returns a human-friendly fallback on failure. This helps callers avoid defensive null checks.

runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java (1)

105-108: Remove unreachable fallback for port.

After validation, port is always > 0; simplify.

-        System.setProperty("socksProxyPort", String.valueOf(port > 0 ? port : 1080));
+        System.setProperty("socksProxyPort", String.valueOf(port));
runelite-client/src/main/java/net/runelite/client/proxy/ProxyChecker.java (2)

23-26: Add fallback IP endpoints for resiliency.

Avoid single-point dependency on microbot.cloud by adding common IP echo services.

-        List<String> endpoints = List.of(
-                "https://microbot.cloud/api/network/ip"
-        );
+        List<String> endpoints = List.of(
+                "https://microbot.cloud/api/network/ip",
+                "https://api.ipify.org",
+                "https://ipv4.icanhazip.com"
+        );

14-16: Support IPv6 addresses as well.

Some proxies/networks expose IPv6 egress; broaden validation (e.g., Guava InetAddresses).

Example change (requires Guava, commonly present in RuneLite):

import com.google.common.net.InetAddresses;

// ...
String ip = res.body().string().trim();
if (!InetAddresses.isInetAddress(ip)) {
    log.warn("Invalid IP payload from {}: '{}'", url, ip);
    return null;
}

Also applies to: 50-53

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d0af195 and 5a88cb1.

📒 Files selected for processing (3)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/proxy/ProxyChecker.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-31T19:20:28.774Z
Learnt from: chsami
PR: chsami/Microbot#1455
File: runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java:224-233
Timestamp: 2025-08-31T19:20:28.774Z
Learning: The RuneLite project only supports SOCKS5 proxies, and all traffic is expected to go through the proxy when configured.

Applied to files:

  • runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java
🧬 Code graph analysis (2)
runelite-client/src/main/java/net/runelite/client/proxy/ProxyChecker.java (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java (1)
  • Slf4j (16-65)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java (1)
runelite-client/src/main/java/net/runelite/client/proxy/ProxyChecker.java (1)
  • Slf4j (12-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Build
🔇 Additional comments (3)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java (1)

57-63: EDT handoff is correct.

Good job ensuring UI updates happen on the Swing EDT.

runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java (1)

64-69: SOCKS-only enforcement aligns with project policy.

Rejecting HTTP(S) proxies matches the stated requirement that only SOCKS5 is supported and all traffic must go through the proxy.

runelite-client/src/main/java/net/runelite/client/proxy/ProxyChecker.java (1)

42-59: Networking flow looks solid.

Good use of try-with-resources, status/body checks, trimming, and targeted logging.

Comment on lines +1 to +65
package net.runelite.client.plugins.microbot;

import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import javax.swing.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
* Client to fetch random facts from the microbot API.
*/
@Slf4j
public class RandomFactClient {
private static final String MICROBOT_API_URL = "https://microbot.cloud/api";
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();

/**
* Fetches a random fact from the microbot API.
* @return random fact string or error message
*/
public static String getRandomFact() {
try {
Request request = new Request.Builder()
.url(MICROBOT_API_URL + "/fact/random")
.build();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful() || response.body() == null) {
return "Failed to fetch random fact";
}
return response.body().string();
}
} catch (Exception e) {
return null;
}
}

/**
* Fetches a random fact asynchronously and invokes the callback on the Swing EDT.
* @param callback
*/
public static void getRandomFactAsync(Consumer<String> callback) {
CompletableFuture.supplyAsync(() -> {
try {
return getRandomFact();
} catch (Exception e) {
log.error("Error in async fact fetching", e);
}
return null;
}).thenAccept(fact -> {
// Ensure UI update happens on EDT
SwingUtilities.invokeLater(() -> callback.accept(fact));
}).exceptionally(throwable -> {
log.error("Async fact fetching failed", throwable);
return null;
});
}
} No newline at end of file
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Quick cross-check: ensure we actually pass the proxy-configured client into RandomFactClient.

If you adopt setClient, verify at startup that it’s invoked with the same OkHttpClient used by ProxyChecker/proxy config.


🏁 Script executed:

#!/bin/bash
# Where is OkHttpClient built and how is RandomFactClient used?
rg -nP --type=java -C3 'new OkHttpClient\.Builder\('
rg -nP --type=java -C3 '\bRandomFactClient\.(getRandomFact|getRandomFactAsync|setClient)\b'

Length of output: 3525


Inject proxy-configured OkHttpClient into RandomFactClient
Add a public static void setClient(OkHttpClient client) to RandomFactClient (in runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java) and invoke it at startup—passing the same OkHttpClient built by RuneLite.buildHttpClient(…)—so that all requests use the proxy settings.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use SOCKS5 for proxies. all of the traffic will pass through it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chsami, thank you for the clarification! That makes perfect sense. Since SOCKS5 proxy configuration is applied at the JVM level and routes all traffic through the proxy automatically, the RandomFactClient will inherently use the proxy settings without needing explicit client injection. This is a much cleaner approach than what I initially suggested.

I appreciate you explaining how the proxy system works in your implementation.

🐰
👂 <- learned something new!
/||
/\


✏️ Learnings added
Learnt from: chsami
PR: chsami/Microbot#1455
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java:1-65
Timestamp: 2025-08-31T19:37:01.637Z
Learning: In the Microbot/RuneLite project, SOCKS5 proxy configuration is applied at the JVM level, meaning all network traffic (including HTTP clients like RandomFactClient) automatically routes through the configured proxy without requiring explicit client configuration injection.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: chsami
PR: chsami/Microbot#1455
File: runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java:224-233
Timestamp: 2025-08-31T19:20:28.793Z
Learning: The RuneLite project only supports SOCKS5 proxies, and all traffic is expected to go through the proxy when configured.

Comment on lines +19 to +22
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Route HTTP through the shared, proxy-configured OkHttpClient (don’t bypass proxy).

This private static client ignores the proxy stack introduced in this PR, so fact fetches may fail behind SOCKS5 or skip configured auth. Allow injecting the proxy-configured client (or reuse the app-wide one) and add a total call timeout.

Apply:

-    private static final OkHttpClient client = new OkHttpClient.Builder()
+    private static volatile OkHttpClient client = new OkHttpClient.Builder()
             .connectTimeout(10, TimeUnit.SECONDS)
             .readTimeout(10, TimeUnit.SECONDS)
+            .callTimeout(10, TimeUnit.SECONDS)
             .build();
+
+    /** Optionally set by ProxyConfiguration to reuse the proxy-configured/shared client. */
+    public static void setClient(OkHttpClient okHttpClient) {
+        if (okHttpClient != null) {
+            client = okHttpClient;
+        }
+    }

Follow-up: from your proxy/bootstrap path, call RandomFactClient.setClient(proxyConfiguredOkHttpClient). I can wire this where you initialize the proxy if you’d like.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
private static volatile OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.callTimeout(10, TimeUnit.SECONDS)
.build();
/** Optionally set by ProxyConfiguration to reuse the proxy-configured/shared client. */
public static void setClient(OkHttpClient okHttpClient) {
if (okHttpClient != null) {
client = okHttpClient;
}
}

Comment on lines +22 to +34
public static String getDetectedIp(OkHttpClient okHttpClient) {
List<String> endpoints = List.of(
"https://microbot.cloud/api/network/ip"
);

for (String url : endpoints) {
String ip = fetchIp(okHttpClient, url);
if (ip != null) {
return ip;
}
}
return "";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Ensure client timeouts are set at call sites.

This class relies on the provided OkHttpClient; verify connect/read/write timeouts are non-default and sane (e.g., 5–15s) to avoid hangs when proxies misbehave.

Run to locate builders and timeout settings and the call sites:

Also applies to: 42-59


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -nP -C2 --type=java 'new\s+OkHttpClient\.Builder\s*\('
rg -nP --type=java 'connectTimeout\s*\('
rg -nP --type=java 'readTimeout\s*\('
rg -nP --type=java 'writeTimeout\s*\('
rg -nP -C3 --type=java 'ProxyChecker\.getDetectedIp\s*\('

Length of output: 2385


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -nF 'getDetectedIp' -n runelite-client/src

Length of output: 474


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -nP -C10 --type=java 'static OkHttpClient buildHttpClient' runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java
rg -nP -C10 --type=java 'static OkHttpClient buildHttpClient' runelite-client/src/main/java/net/runelite/client/RuneLite.java

Length of output: 1526


Explicitly set connect, read, and write timeouts on OkHttpClient builders

  • In runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java (buildHttpClient): only pingInterval and interceptor are configured—add .connectTimeout(...), .readTimeout(...), .writeTimeout(...) (e.g. 10s).
  • In runelite-client/src/main/java/net/runelite/client/RuneLite.java (buildHttpClient): likewise add explicit timeouts to avoid relying solely on OkHttp defaults.
🤖 Prompt for AI Agents
In runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java around
the buildHttpClient method and in
runelite-client/src/main/java/net/runelite/client/RuneLite.java around its
buildHttpClient method (both referenced in the review), the OkHttpClient
builders only set pingInterval and interceptors and rely on OkHttp defaults for
timeouts; update each OkHttpClient.Builder to explicitly set connectTimeout,
readTimeout, and writeTimeout (for example to 10 seconds) so the client doesn't
depend on defaults—add the three timeout calls to the builder chain alongside
the existing settings.

Comment on lines +28 to +29
URI uri = URI.create(options.valueOf(proxyInfo));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle invalid proxy URIs gracefully (avoid crash on bad input).

URI.create(...) throws IllegalArgumentException on malformed values; show a friendly message and exit instead of bubbling an uncaught exception.

-        URI uri = URI.create(options.valueOf(proxyInfo));
+        URI uri;
+        try {
+            uri = URI.create(String.valueOf(options.valueOf(proxyInfo)));
+        } catch (IllegalArgumentException e) {
+            Microbot.showMessage("Invalid proxy URI. Expected format: socks5://[user[:pass]@]host:port");
+            System.exit(1);
+            return;
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
URI uri = URI.create(options.valueOf(proxyInfo));
URI uri;
try {
uri = URI.create(String.valueOf(options.valueOf(proxyInfo)));
} catch (IllegalArgumentException e) {
Microbot.showMessage("Invalid proxy URI. Expected format: socks5://[user[:pass]@]host:port");
System.exit(1);
return;
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java
around lines 28-29, replace the direct call to
URI.create(options.valueOf(proxyInfo)) with code that wraps URI creation in a
try/catch for IllegalArgumentException (and optionally NullPointerException),
and on catch log or print a clear, user-friendly error indicating the proxy URI
is malformed (include the bad value), then exit with a non-zero status instead
of allowing the uncaught exception to propagate; ensure normal flow continues
when the URI is valid.

Comment on lines +30 to +33
if (options.has("proxy-type")) {
Microbot.showMessage("Proxy type is no longer supported, please use the format -proxy=socks://user:pass@host:port or http://user:pass@host:port");
System.exit(1);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Contradictory user guidance: UI suggests HTTP supported while code rejects HTTP(S).

The message at Line 31 advertises http:// as valid, but validateProxyScheme disallows HTTP/HTTPS. Fix the message to avoid user confusion.

-            Microbot.showMessage("Proxy type is no longer supported, please use the format -proxy=socks://user:pass@host:port or http://user:pass@host:port");
+            Microbot.showMessage("Proxy type flag is no longer supported. Use -proxy=socks5://[user[:pass]@]host:port (HTTP/HTTPS proxies are not supported).");

Also applies to: 58-69

🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java
around lines 30-33 and 58-69, the user-facing message says http:// is supported
while validateProxyScheme rejects HTTP/HTTPS; update the messages to reflect the
actual supported proxy schemes (e.g., only socks:// and socks5:// with optional
user:pass@host:port) or otherwise align validateProxyScheme to accept
http/https—modify the strings to remove any reference to http:// or https:// (or
change validation to permit them) so the UI guidance matches the code behavior.

Comment on lines +35 to +41
String host = uri.getHost();
String scheme = Optional.ofNullable(uri.getScheme()).orElse("").toLowerCase(Locale.ROOT);

validateProxyScheme(scheme);

int port = validatePort(uri.getPort());

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Validate proxy host presence.

uri.getHost() can be null/blank; add a guard to prevent setting empty system properties.

-        String host = uri.getHost();
+        String host = uri.getHost();
         String scheme = Optional.ofNullable(uri.getScheme()).orElse("").toLowerCase(Locale.ROOT);
 
         validateProxyScheme(scheme);
 
+        if (host == null || host.isBlank()) {
+            Microbot.showMessage("Invalid proxy host");
+            System.exit(1);
+        }
         int port = validatePort(uri.getPort());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String host = uri.getHost();
String scheme = Optional.ofNullable(uri.getScheme()).orElse("").toLowerCase(Locale.ROOT);
validateProxyScheme(scheme);
int port = validatePort(uri.getPort());
String host = uri.getHost();
String scheme = Optional.ofNullable(uri.getScheme()).orElse("").toLowerCase(Locale.ROOT);
validateProxyScheme(scheme);
if (host == null || host.isBlank()) {
Microbot.showMessage("Invalid proxy host");
System.exit(1);
}
int port = validatePort(uri.getPort());
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java
around lines 35 to 41, uri.getHost() can be null or blank so add a guard to
validate host presence before using it or setting system properties: after
retrieving host, check if host is null or host.trim().isEmpty() and if so throw
an IllegalArgumentException (or return/skip proxy setup) with a clear message;
only proceed to call validateProxyScheme/validatePort and set system properties
when the host passes this non-empty check.

Comment on lines +46 to +50
configureProxy(host, port);

if (user != null) {
setupAuthenticator(user, pass);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Limit Authenticator to proxy requests and target host/port (avoid credential leakage).

Current Authenticator returns credentials for any request type/host; restrict to RequestorType.PROXY and the configured proxy host/port.

-        if (user != null) {
-            setupAuthenticator(user, pass);
-        }
+        if (user != null) {
+            setupAuthenticator(user, pass, host, port);
+        }
@@
-    private static void setupAuthenticator(String user, String pass) {
+    private static void setupAuthenticator(String user, String pass, String host, int port) {
         Authenticator.setDefault(new Authenticator() {
             @Override
             protected PasswordAuthentication getPasswordAuthentication() {
-                return new PasswordAuthentication(user, pass != null ? pass.toCharArray() : new char[0]);
+                if (getRequestorType() == RequestorType.PROXY
+                        && host.equalsIgnoreCase(getRequestingHost())
+                        && getRequestingPort() == port) {
+                    return new PasswordAuthentication(user, pass != null ? pass.toCharArray() : new char[0]);
+                }
+                return null;
             }
         });
     }

Also applies to: 115-122

🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java
around lines 46-50 and 115-122, the Authenticator currently returns credentials
for any request; restrict it to only respond to proxy authentication for the
configured proxy host and port. Update the
Authenticator.getPasswordAuthentication() to check that getRequestorType() ==
RequestorType.PROXY and that getRequestingHost()/getRequestingPort() match the
configured proxy host and port (and return null otherwise), and ensure the
configured host/port are accessible to the Authenticator (use final fields or
capture them in the closure) before returning a new PasswordAuthentication(user,
pass.toCharArray()) when and only when those checks pass.

Comment on lines +76 to +82
private static int validatePort(int port) {
if (port <= 0) {
Microbot.showMessage("Invalid proxy port");
System.exit(1);
}
return port;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enforce valid TCP port range (1–65535).

Protect against out-of-range ports.

-    private static int validatePort(int port) {
-        if (port <= 0) {
-            Microbot.showMessage("Invalid proxy port");
-            System.exit(1);
-        }
-        return port;
-    }
+    private static int validatePort(int port) {
+        if (port <= 0 || port > 65535) {
+            Microbot.showMessage("Invalid proxy port (must be 1-65535)");
+            System.exit(1);
+        }
+        return port;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static int validatePort(int port) {
if (port <= 0) {
Microbot.showMessage("Invalid proxy port");
System.exit(1);
}
return port;
}
private static int validatePort(int port) {
if (port <= 0 || port > 65535) {
Microbot.showMessage("Invalid proxy port (must be 1-65535)");
System.exit(1);
}
return port;
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java
around lines 76 to 82, the validatePort method currently only checks port > 0;
update it to enforce the full valid TCP port range (1–65535) by checking that
port >= 1 && port <= 65535, and if the value is outside that range call
Microbot.showMessage with a clear error including the invalid port value and
then terminate (System.exit(1)) or otherwise abort initialization; return the
port when valid.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java (2)

4-4: Build breaker: hard import to missing RandomFactClient.

This import fails CI if RandomFactClient isn’t present. Make it optional (reflection) and drop the import.

Apply alongside the scheduling fixes:

- import net.runelite.client.plugins.microbot.RandomFactClient;

61-63: Thread leak + shadowing + lack of optionality for RandomFactClient.

  • Local var shadows the static executor, so stop() can’t shut it down.
  • The pool uses non-daemon threads.
  • Missing class or runtime errors will kill the scheduled task.

Apply this diff (pairs with removing the import at Line 4):

@@
-    private static ScheduledExecutorService scheduledRandomFactExecutorService;
-    private static ScheduledFuture<?> scheduledRandomFactFuture;
+    private static ScheduledExecutorService scheduledRandomFactExecutor;
+    private static ScheduledFuture<?> scheduledRandomFactFuture;
@@
-        ScheduledExecutorService scheduledRandomFactExecutorService = Executors.newSingleThreadScheduledExecutor();
-        scheduledRandomFactFuture = scheduledRandomFactExecutorService.scheduleAtFixedRate(
-                () -> {
-                    RandomFactClient.getRandomFactAsync(SplashScreen::setFact);
-                },
-                0, 20, TimeUnit.SECONDS);
+        scheduledRandomFactExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
+            Thread t = new Thread(r, "SplashScreenFacts");
+            t.setDaemon(true);
+            return t;
+        });
+        scheduledRandomFactFuture = scheduledRandomFactExecutor.scheduleAtFixedRate(() -> {
+            try {
+                Class<?> cls = Class.forName("net.runelite.client.plugins.microbot.RandomFactClient");
+                var m = cls.getMethod("getRandomFactAsync", java.util.function.Consumer.class);
+                m.invoke(null, (java.util.function.Consumer<String>) SplashScreen::setFact);
+            } catch (Throwable t) {
+                log.debug("RandomFactClient unavailable or failed, skipping", t);
+            }
+        }, 0, 20, TimeUnit.SECONDS);
@@
-            if (scheduledRandomFactFuture != null) {
-                scheduledRandomFactFuture.cancel(true);
-            }
-            if (scheduledRandomFactExecutorService != null) {
-                scheduledRandomFactExecutorService.shutdownNow();
-                scheduledRandomFactExecutorService = null;
-            }
+            if (scheduledRandomFactFuture != null) {
+                scheduledRandomFactFuture.cancel(true);
+                scheduledRandomFactFuture = null;
+            }
+            if (scheduledRandomFactExecutor != null) {
+                scheduledRandomFactExecutor.shutdownNow();
+                scheduledRandomFactExecutor = null;
+            }

Also applies to: 271-277, 323-330

🧹 Nitpick comments (4)
runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java (4)

95-99: Close button should clean up before exiting.

Call stop() first to cancel the scheduled task/executor.

-            public void mouseClicked(MouseEvent e) {
-                // Stop the application
-                System.exit(0);
-            }
+            public void mouseClicked(MouseEvent e) {
+                // Graceful shutdown
+                SplashScreen.stop();
+                System.exit(0);
+            }

53-59: Let facts update even if the splash isn’t instantiated yet.

Fire the event only when INSTANCE exists; always update the value.

-    public static void setFact(String newFact) {
-        if (INSTANCE != null && newFact != null && !String.valueOf(factValue).equals(newFact)) {
-            String oldValue = factValue;
-            factValue = newFact;
-            INSTANCE.pcs.firePropertyChange("fact", oldValue, newFact);
-        }
-    }
+    public static void setFact(String newFact) {
+        if (newFact == null || String.valueOf(factValue).equals(newFact)) return;
+        String oldValue = factValue;
+        factValue = newFact;
+        if (INSTANCE != null) {
+            INSTANCE.pcs.firePropertyChange("fact", oldValue, newFact);
+        }
+    }

64-67: Unused helper.

escape(...) is unused after switching to JTextArea.

-    static String escape(String s) {
-        return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
-    }
+    // (removed) unused

271-277: Option: reuse the app’s single-threaded scheduler to avoid extra threads.

If accessible at this layer, prefer the central ScheduledExecutorService configured in RuneLiteModule to keep all async tasks serialized and avoid thread proliferation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5a88cb1 and 8ca05cb.

📒 Files selected for processing (1)
  • runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-23T16:17:37.615Z
Learnt from: g-mason0
PR: chsami/Microbot#1423
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java:366-389
Timestamp: 2025-08-23T16:17:37.615Z
Learning: The ScheduledExecutorService injected into MicrobotPluginManager (and other RuneLite classes) is configured as a single-threaded executor via Executors.newSingleThreadScheduledExecutor() at RuneLiteModule.java line 130, wrapped in ExecutorServiceExceptionLogger. This ensures all async tasks execute sequentially, preventing race conditions.

Applied to files:

  • runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java
🔇 Additional comments (1)
runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java (1)

341-353: Guard against divide-by-zero in progress math.

If total can be 0, this will throw and also show “/ 0”.

-        stage(startProgress + ((endProgress - startProgress) * done / total), actionText, subActionText, progress);
+        int denom = Math.max(total, 1);
+        stage(startProgress + ((endProgress - startProgress) * (double) done / denom), actionText, subActionText, progress);

If total is legitimately 0, consider rendering “done / ?” instead.

@chsami chsami merged commit b69dcc5 into development Aug 31, 2025
3 checks passed
@chsami chsami mentioned this pull request Sep 3, 2025
@chsami chsami deleted the chsami/proxy_splashsscreen_rework branch September 10, 2025 06:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants