From 3bd896c6fb65fc93006912eacad6695608ee6e0e Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Mon, 11 May 2026 23:41:52 -0300 Subject: [PATCH 01/23] add taskapp credentials config options --- .../CollectionLogMasterConfig.java | 74 ++++++++++++++++--- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java b/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java index 27632bf7..0c4b5ce3 100644 --- a/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java +++ b/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java @@ -11,10 +11,56 @@ public interface CollectionLogMasterConfig extends Config { String CONFIG_GROUP = "collection-log-master"; + String TASK_APP_SECTION = "taskApp"; + String TASK_APP_USERNAME = TASK_APP_SECTION + ".username"; + String TASK_APP_PASSWORD = TASK_APP_SECTION + ".password"; + + String GENERAL_SECTION = "general"; + String PLUGIN_VERSION_KEY = "plugin-version"; String IS_LMS_ENABLED_KEY = "isLMSEnabled"; String IS_COMMAND_ENABLED_KEY = "isCommandEnabled"; + @ConfigSection( + name = "TaskApp Credentials", + description = "The credentials for your account in TaskApp (osrstaskapp.com)", + position = 10 + ) + String taskAppSection = TASK_APP_SECTION; + + @ConfigItem( + keyName = TASK_APP_USERNAME, + name = "Username", + description = "Your account's username in TaskApp (osrstaskapp.com)", + section = taskAppSection, + position = 11 + ) + default String username() + { + return ""; + } + + @ConfigItem( + keyName = TASK_APP_PASSWORD, + name = "Password", + description = "Your account's password in TaskApp (osrstaskapp.com)", + section = taskAppSection, + secret = true, + position = 12 + ) + default String password() + { + return ""; + } + + + @ConfigSection( + name = "General", + description = "General settings to control the plugin's behavior and appearance", + position = 20 + ) + String generalSection = GENERAL_SECTION; + @Range( min = 1000, max = 10000 @@ -24,7 +70,8 @@ public interface CollectionLogMasterConfig extends Config keyName = "rollTime", name = "Roll Time", description = "How long new tasks will take to roll", - position = 1 + section = generalSection, + position = 21 ) default int rollTime() { @@ -34,8 +81,9 @@ default int rollTime() @ConfigItem( keyName = "rollPastCompleted", name = "Roll past completed", - description = "When rolling tasks, include those you've already completed in the roll animation. Helpful when you're getting to the end of a tier!", - position = 2 + description = "Include tasks you've already completed in the roll animation. Helpful when you're getting to the end of a tier!", + section = generalSection, + position = 22 ) default boolean rollPastCompleted() { @@ -46,7 +94,8 @@ default boolean rollPastCompleted() keyName = "hideBelow", name = "Hide Tasks Below", description = "Disabled the showing up/assigning of tasks at or below the specified tier", - position = 3 + section = generalSection, + position = 23 ) default TaskTier hideBelow() { @@ -57,7 +106,8 @@ default TaskTier hideBelow() keyName = "displayCurrentTaskOverlay", name = "Display current task overlay", description = "Enable an overlay showing the currently assigned task (when one exists)", - position = 5 + section = generalSection, + position = 24 ) default boolean displayCurrentTaskOverlay() { @@ -68,7 +118,8 @@ default boolean displayCurrentTaskOverlay() keyName = "dynamicTaskImages", name = "Dynamic task images", description = "Display dynamic task images based on required/acquired items", - position = 6 + section = generalSection, + position = 25 ) default DynamicTaskImages dynamicTaskImages() { @@ -79,7 +130,8 @@ default DynamicTaskImages dynamicTaskImages() keyName = IS_LMS_ENABLED_KEY, name = "Enable LMS tasks", description = "Whether to include LMS tasks in the list.", - position = 7 + section = generalSection, + position = 26 ) default boolean isLMSEnabled() { @@ -89,16 +141,16 @@ default boolean isLMSEnabled() @ConfigSection( name = "!taskman Command", description = "Configuration options for the !taskman command", - position = 8 + position = 30 ) - String command = "command"; + String commandSection = "command"; @ConfigItem( keyName = IS_COMMAND_ENABLED_KEY, name = "Enable command", description = "When you or others type !taskman in the chat, it will be replaced by your current task status", - section = command, - position = 0 + section = commandSection, + position = 31 ) default boolean isCommandEnabled() { From 0d790fb5ac1d2983251021f377f0f63866382199 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Fri, 15 May 2026 22:42:31 -0300 Subject: [PATCH 02/23] simplify debouncer and allow setting custom delay --- .../util/SimpleDebouncer.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/collectionlogmaster/util/SimpleDebouncer.java b/src/main/java/com/collectionlogmaster/util/SimpleDebouncer.java index 73baa954..578ead15 100644 --- a/src/main/java/com/collectionlogmaster/util/SimpleDebouncer.java +++ b/src/main/java/com/collectionlogmaster/util/SimpleDebouncer.java @@ -5,25 +5,25 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import lombok.Setter; +import lombok.experimental.Accessors; public class SimpleDebouncer { - public interface Callback { - void call(); - } - - Future future; + private Future future; @Inject private ScheduledExecutorService executorService; - private final static int MS_DELAY = 500; + @Setter + @Accessors(chain = true) + private int delay = 500; - public synchronized void debounce(Callback cb) { + public synchronized void debounce(Runnable cb) { if (future != null) { future.cancel(false); future = null; } - future = executorService.schedule(cb::call, MS_DELAY, TimeUnit.MILLISECONDS); + future = executorService.schedule(cb, delay, TimeUnit.MILLISECONDS); } } From ca936bf580f4703ea166deb7ee332feeede62d4f Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Fri, 15 May 2026 22:57:57 -0300 Subject: [PATCH 03/23] move transparent.png sprite to the correct folder --- .../ui/{ => sprites}/transparent.png | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/com/collectionlogmaster/ui/{ => sprites}/transparent.png (100%) diff --git a/src/main/resources/com/collectionlogmaster/ui/transparent.png b/src/main/resources/com/collectionlogmaster/ui/sprites/transparent.png similarity index 100% rename from src/main/resources/com/collectionlogmaster/ui/transparent.png rename to src/main/resources/com/collectionlogmaster/ui/sprites/transparent.png From ee165ed79f7db4aeb690b0aa625aed8887608aef Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Fri, 15 May 2026 22:58:16 -0300 Subject: [PATCH 04/23] use taskapp as data source --- .../CollectionLogMasterConfig.java | 13 -- .../CollectionLogMasterPlugin.java | 2 +- .../command/DevCommandsManager.java | 8 +- .../command/TaskmanCommandManager.java | 7 +- .../domain/savedata/BaseSaveData.java | 12 -- .../domain/savedata/SaveData.java | 24 --- .../domain/savedata/SaveDataUpdater.java | 135 ----------------- .../domain/savedata/v0/V0SaveData.java | 35 ----- .../domain/savedata/v0/V0Task.java | 11 -- .../domain/savedata/v0/V0TaskPointer.java | 14 -- .../domain/savedata/v1/V1SaveData.java | 34 ----- .../domain/savedata/v1/V1TaskPointer.java | 16 -- .../domain/savedata/v2/V2SaveData.java | 27 ---- .../synchronization/SyncService.java | 58 ++++---- .../task/SaveDataStorage.java | 127 ---------------- .../task/TaskListStorage.java | 47 ------ .../taskapp/TaskAppAuthInterceptor.java | 64 ++++++++ .../taskapp/TaskAppClient.java | 96 ++++++++++++ .../taskapp/TaskAppState.java | 13 ++ .../taskapp/TaskAppStateStorage.java | 52 +++++++ .../taskapp/TaskListStorage.java | 31 ++++ .../{task => taskapp}/TaskService.java | 139 ++++++------------ .../taskapp/domain/CompletedTask.java | 14 ++ .../response/GenerateTaskResponse.java | 8 + .../taskapp/response/LoginResponse.java | 8 + .../taskapp/response/TaskListResponse.java | 11 ++ .../taskapp/response/UserProfileResponse.java | 14 ++ .../collectionlogmaster/ui/TaskOverlay.java | 2 +- .../ui/component/MainTabbedContainer.java | 2 +- .../ui/component/TaskComponent.java | 17 ++- .../ui/component/TaskDashboard.java | 44 +++--- .../ui/component/TaskInfo.java | 10 +- .../ui/component/TaskList.java | 2 +- .../util/GsonOverride.java | 2 + .../collectionlogmaster/util/HttpClient.java | 66 ++++++--- 35 files changed, 481 insertions(+), 684 deletions(-) delete mode 100644 src/main/java/com/collectionlogmaster/domain/savedata/BaseSaveData.java delete mode 100644 src/main/java/com/collectionlogmaster/domain/savedata/SaveData.java delete mode 100644 src/main/java/com/collectionlogmaster/domain/savedata/SaveDataUpdater.java delete mode 100644 src/main/java/com/collectionlogmaster/domain/savedata/v0/V0SaveData.java delete mode 100644 src/main/java/com/collectionlogmaster/domain/savedata/v0/V0Task.java delete mode 100644 src/main/java/com/collectionlogmaster/domain/savedata/v0/V0TaskPointer.java delete mode 100644 src/main/java/com/collectionlogmaster/domain/savedata/v1/V1SaveData.java delete mode 100644 src/main/java/com/collectionlogmaster/domain/savedata/v1/V1TaskPointer.java delete mode 100644 src/main/java/com/collectionlogmaster/domain/savedata/v2/V2SaveData.java delete mode 100644 src/main/java/com/collectionlogmaster/task/SaveDataStorage.java delete mode 100644 src/main/java/com/collectionlogmaster/task/TaskListStorage.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java rename src/main/java/com/collectionlogmaster/{task => taskapp}/TaskService.java (53%) create mode 100644 src/main/java/com/collectionlogmaster/taskapp/domain/CompletedTask.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/response/GenerateTaskResponse.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/response/LoginResponse.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/response/TaskListResponse.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java diff --git a/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java b/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java index 0c4b5ce3..8e74a950 100644 --- a/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java +++ b/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java @@ -18,7 +18,6 @@ public interface CollectionLogMasterConfig extends Config String GENERAL_SECTION = "general"; String PLUGIN_VERSION_KEY = "plugin-version"; - String IS_LMS_ENABLED_KEY = "isLMSEnabled"; String IS_COMMAND_ENABLED_KEY = "isCommandEnabled"; @ConfigSection( @@ -126,18 +125,6 @@ default DynamicTaskImages dynamicTaskImages() return DynamicTaskImages.COMPLETE; } - @ConfigItem( - keyName = IS_LMS_ENABLED_KEY, - name = "Enable LMS tasks", - description = "Whether to include LMS tasks in the list.", - section = generalSection, - position = 26 - ) - default boolean isLMSEnabled() - { - return true; - } - @ConfigSection( name = "!taskman Command", description = "Configuration options for the !taskman command", diff --git a/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java b/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java index da520c22..15444abd 100644 --- a/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java +++ b/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java @@ -4,7 +4,7 @@ import com.collectionlogmaster.command.TaskmanCommandManager; import com.collectionlogmaster.input.MouseManager; import com.collectionlogmaster.synchronization.clog.CollectionLogService; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.InterfaceManager; import com.collectionlogmaster.ui.TaskOverlay; import com.collectionlogmaster.util.GsonOverride; diff --git a/src/main/java/com/collectionlogmaster/command/DevCommandsManager.java b/src/main/java/com/collectionlogmaster/command/DevCommandsManager.java index e0288e79..87ccd99a 100644 --- a/src/main/java/com/collectionlogmaster/command/DevCommandsManager.java +++ b/src/main/java/com/collectionlogmaster/command/DevCommandsManager.java @@ -2,8 +2,8 @@ import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.task.SaveDataStorage; -import com.collectionlogmaster.task.TaskListStorage; +import com.collectionlogmaster.taskapp.TaskAppStateStorage; +import com.collectionlogmaster.taskapp.TaskListStorage; import com.collectionlogmaster.util.EventBusSubscriber; import javax.inject.Inject; import javax.inject.Named; @@ -18,7 +18,7 @@ public class DevCommandsManager extends EventBusSubscriber { private final String SET_ACTIVE_TASK_COMMAND = "set-active-task"; @Inject - private SaveDataStorage saveDataStorage; + private TaskAppStateStorage taskAppStateStorage; @Inject private TaskListStorage taskListStorage; @@ -48,7 +48,7 @@ private void executeSecActiveTaskCommand(String[] args) { String taskId = task.getId(); if (taskId.startsWith(taskPrefix)) { log.debug("Setting active task to {}", taskId); - saveDataStorage.get().setActiveTaskId(taskId); + taskAppStateStorage.get().setActiveTaskId(taskId); return; } } diff --git a/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java b/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java index d59a1132..bcc6f6b0 100644 --- a/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java +++ b/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java @@ -7,7 +7,7 @@ import com.collectionlogmaster.domain.TaskTier; import com.collectionlogmaster.domain.command.CommandRequest; import com.collectionlogmaster.domain.command.CommandResponse; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.util.EventBusSubscriber; import com.collectionlogmaster.util.HttpClient; import com.collectionlogmaster.util.SimpleDebouncer; @@ -112,7 +112,7 @@ private void executeCommand(ChatMessage chatMessage, String message) { } HttpUrl url = baseApiUrl.newBuilder().addPathSegment(senderName).build(); - httpClient.getHttpRequestAsync(url.toString(), CommandResponse.class) + httpClient.get(url, CommandResponse.class) .thenAccept(res -> clientThread.invokeLater(() -> replaceChatMessage(chatMessage, res)) ); @@ -145,7 +145,8 @@ public void updateServerImmediately() { float currentProgress = taskService.getProgress().get(currentTier) * 100; CommandRequest data = new CommandRequest(taskId, taskService.getCurrentTier().displayName, (int) currentProgress); - httpClient.putHttpRequestAsync(url.toString(), GSON.toJson(data), null); + + httpClient.put(url, GSON.toJson(data), null); } private void replaceChatMessage(ChatMessage chatMessage, CommandResponse res) { diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/BaseSaveData.java b/src/main/java/com/collectionlogmaster/domain/savedata/BaseSaveData.java deleted file mode 100644 index 486a2fa1..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/BaseSaveData.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.collectionlogmaster.domain.savedata; - -import lombok.Getter; -import lombok.ToString; - -import javax.annotation.Nullable; - -@ToString -@Getter -public class BaseSaveData { - protected @Nullable Integer version = null; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/SaveData.java b/src/main/java/com/collectionlogmaster/domain/savedata/SaveData.java deleted file mode 100644 index 0043072e..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/SaveData.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.collectionlogmaster.domain.savedata; - -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import javax.annotation.Nullable; -import java.util.HashSet; -import java.util.Set; - -@Getter -@ToString -public class SaveData extends BaseSaveData { - public final static int VERSION = 3; - - public SaveData() { - this.version = VERSION; - } - - @Setter - private @Nullable String activeTaskId = null; - - private final Set completedTasks = new HashSet<>(); -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/SaveDataUpdater.java b/src/main/java/com/collectionlogmaster/domain/savedata/SaveDataUpdater.java deleted file mode 100644 index a016aa74..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/SaveDataUpdater.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.collectionlogmaster.domain.savedata; - -import com.google.gson.reflect.TypeToken; -import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.domain.savedata.v0.V0SaveData; -import com.collectionlogmaster.domain.savedata.v0.V0Task; -import com.collectionlogmaster.domain.savedata.v0.V0TaskPointer; -import com.collectionlogmaster.domain.savedata.v1.V1SaveData; -import com.collectionlogmaster.domain.savedata.v1.V1TaskPointer; -import com.collectionlogmaster.domain.savedata.v2.V2SaveData; -import com.collectionlogmaster.task.SaveDataStorage; -import com.collectionlogmaster.task.TaskService; -import com.collectionlogmaster.util.FileUtils; -import lombok.extern.slf4j.Slf4j; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.lang.reflect.Type; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import static com.collectionlogmaster.util.GsonOverride.GSON; - -@Singleton -@Slf4j -@SuppressWarnings("deprecation") -public class SaveDataUpdater { - @Inject - private SaveDataStorage saveDataStorage; - - @Inject - private TaskService taskService; - - public SaveData update(String json) { - BaseSaveData base = GSON.fromJson(json, BaseSaveData.class); - if (base == null) { - return new SaveData(); - } - - if (base.getVersion() == V0SaveData.VERSION) { - V0SaveData v0Save = GSON.fromJson(json, V0SaveData.class); - return update(update(update(v0Save))); - } - - if (base.getVersion() == V1SaveData.VERSION) { - V1SaveData v1Save = GSON.fromJson(json, V1SaveData.class); - return update(update(v1Save)); - } - - if (base.getVersion() == V2SaveData.VERSION) { - V2SaveData v2Save = GSON.fromJson(json, V2SaveData.class); - return update(v2Save); - } - - if (base.getVersion() == SaveData.VERSION) { - return GSON.fromJson(json, SaveData.class); - } - - log.warn("Could not figure out save data version for json {}", json); - return new SaveData(); - } - - private SaveData update(V2SaveData v2Save) { - saveDataStorage.saveBackup(v2Save); - SaveData newSave = new SaveData(); - - newSave.getCompletedTasks().addAll(v2Save.getCompletedTasks()); - - Task activeTask = v2Save.getActiveTask(); - if (activeTask != null) { - newSave.setActiveTaskId(activeTask.getId()); - } - - return newSave; - } - - private V2SaveData update(V1SaveData v1Save) { - saveDataStorage.saveBackup(v1Save); - V2SaveData newSave = new V2SaveData(); - - V1TaskPointer v1ActiveTaskPointer = v1Save.getActiveTaskPointer(); - if (v1ActiveTaskPointer != null) { - newSave.setActiveTask(v1ActiveTaskPointer.getTask()); - } - - Set newCompletedTasks = newSave.getCompletedTasks(); - Set v1CompletedTasks = v1Save.getProgress().entrySet().stream() - .flatMap(entry -> entry.getValue().stream()) - .collect(Collectors.toSet()); - - newCompletedTasks.addAll(v1CompletedTasks); - - return newSave; - } - - private V1SaveData update(V0SaveData v0Save) { - saveDataStorage.saveBackup(v0Save); - V1SaveData newSave = new V1SaveData(); - - Type mapType = new TypeToken>>() {}.getType(); - Map> v0MigrationData = - FileUtils.loadResource("domain/savedata/v0-migration.json", mapType);; - - Map> v0Progress = v0Save.getProgress(); - Map> newProgress = newSave.getProgress(); - - for (TaskTier tier : TaskTier.values()) { - Set v0TierData = v0Progress.get(tier); - Set newTierData = newProgress.get(tier); - Map tierMigrationData = v0MigrationData.get(tier); - - for (Integer v0TaskId : v0TierData) { - if (tierMigrationData.containsKey(v0TaskId)) { - newTierData.add(tierMigrationData.get(v0TaskId)); - } - } - } - - V0TaskPointer v0TaskPointer = v0Save.getActiveTaskPointer(); - if (v0TaskPointer != null) { - V0Task v0Task = v0TaskPointer.getTask(); - String newTaskId = v0MigrationData.get(v0TaskPointer.getTaskTier()).get(v0Task.getId()); - Task newTask = taskService.getTaskById(newTaskId); - - // if we can't find the task, don't set it to avoid problems - if (newTask != null) { - newSave.setActiveTaskPointer(new V1TaskPointer(v0TaskPointer.getTaskTier(), newTask)); - } - } - - return newSave; - } -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0SaveData.java b/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0SaveData.java deleted file mode 100644 index 5cf6f9df..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0SaveData.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v0; - -import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.domain.savedata.BaseSaveData; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -@Getter -@ToString -@Deprecated -public class V0SaveData extends BaseSaveData { - public final static Integer VERSION = null; - - public V0SaveData() { - this.progress = new HashMap<>(); - - for (TaskTier tier : TaskTier.values()) { - this.progress.put(tier, new HashSet<>()); - } - } - - private final Map> progress; - - @Setter - private V0TaskPointer activeTaskPointer; - - @Setter - private TaskTier selectedTier; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0Task.java b/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0Task.java deleted file mode 100644 index bdd285ed..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0Task.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v0; - -import lombok.Getter; - -@Getter -@Deprecated -public class V0Task { - private int id; - private String description; - private int itemID; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0TaskPointer.java b/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0TaskPointer.java deleted file mode 100644 index dcc44f07..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0TaskPointer.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v0; - -import com.collectionlogmaster.domain.TaskTier; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@Deprecated -public class V0TaskPointer { - - private TaskTier taskTier; - private V0Task task; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1SaveData.java b/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1SaveData.java deleted file mode 100644 index d1fbd251..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1SaveData.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v1; - -import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.domain.savedata.BaseSaveData; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -@Getter -@ToString -@Deprecated -public class V1SaveData extends BaseSaveData { - public final static int VERSION = 1; - - public V1SaveData() { - this.version = VERSION; - this.progress = new HashMap<>(); - - for (TaskTier tier : TaskTier.values()) { - this.progress.put(tier, new HashSet<>()); - } - } - - private final Map> progress; - - @Setter - private @Nullable V1TaskPointer activeTaskPointer; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1TaskPointer.java b/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1TaskPointer.java deleted file mode 100644 index 90e44a25..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1TaskPointer.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v1; - -import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.domain.TaskTier; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@AllArgsConstructor -@NoArgsConstructor -@Deprecated -public class V1TaskPointer { - private TaskTier taskTier; - private Task task; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v2/V2SaveData.java b/src/main/java/com/collectionlogmaster/domain/savedata/v2/V2SaveData.java deleted file mode 100644 index 36e00195..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v2/V2SaveData.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v2; - -import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.domain.savedata.BaseSaveData; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import javax.annotation.Nullable; -import java.util.HashSet; -import java.util.Set; - -@Getter -@ToString -@Deprecated -public class V2SaveData extends BaseSaveData { - public final static int VERSION = 2; - - public V2SaveData() { - this.version = VERSION; - } - - private final Set completedTasks = new HashSet<>(); - - @Setter - private @Nullable Task activeTask = null; -} diff --git a/src/main/java/com/collectionlogmaster/synchronization/SyncService.java b/src/main/java/com/collectionlogmaster/synchronization/SyncService.java index c0394d84..ca786a36 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/SyncService.java +++ b/src/main/java/com/collectionlogmaster/synchronization/SyncService.java @@ -1,12 +1,11 @@ package com.collectionlogmaster.synchronization; -import com.collectionlogmaster.CollectionLogMasterPlugin; import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.TaskTier; import com.collectionlogmaster.synchronization.clog.CollectionLogVerifier; import com.collectionlogmaster.synchronization.diary.AchievementDiaryVerifier; import com.collectionlogmaster.synchronization.skill.SkillVerifier; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import javax.inject.Inject; import javax.inject.Singleton; import lombok.NonNull; @@ -20,9 +19,6 @@ public class SyncService { @Inject private Client client; - @Inject - private CollectionLogMasterPlugin plugin; - @Inject private TaskService taskService; @@ -57,30 +53,32 @@ private Boolean verify(Task task) { } public void sync() { - int updatedCount = 0; - for (TaskTier tier : TaskTier.values()) { - for (Task task : taskService.getTierTasks(tier)) { - Boolean isVerified = syncService.verify(task); - if (isVerified == null) { - continue; - } - - boolean taskChanged = isVerified != taskService.isComplete(task.getId()); - if (!taskChanged) { - continue; - } - - taskService.toggleComplete(task.getId()); - - String newStatus = isVerified ? "complete" : "incomplete"; - String msg = String.format("%s tier task '%s' marked as %s", tier.displayName, task.getName(), newStatus); - client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, ""); - - updatedCount++; - } - } - - String msg = String.format("Task synchronization finalized; %d tasks updated", updatedCount); - client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, null); + // TODO: implement syncing + client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Sync disabled", null); +// int updatedCount = 0; +// for (TaskTier tier : TaskTier.values()) { +// for (Task task : taskService.getTierTasks(tier)) { +// Boolean isVerified = syncService.verify(task); +// if (isVerified == null) { +// continue; +// } +// +// boolean taskChanged = isVerified != taskService.isComplete(task.getId()); +// if (!taskChanged) { +// continue; +// } +// +// taskService.toggleComplete(task.getId()); +// +// String newStatus = isVerified ? "complete" : "incomplete"; +// String msg = String.format("%s tier task '%s' marked as %s", tier.displayName, task.getName(), newStatus); +// client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, ""); +// +// updatedCount++; +// } +// } +// +// String msg = String.format("Task synchronization finalized; %d tasks updated", updatedCount); +// client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, null); } } diff --git a/src/main/java/com/collectionlogmaster/task/SaveDataStorage.java b/src/main/java/com/collectionlogmaster/task/SaveDataStorage.java deleted file mode 100644 index ad812297..00000000 --- a/src/main/java/com/collectionlogmaster/task/SaveDataStorage.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.collectionlogmaster.task; - -import com.collectionlogmaster.CollectionLogMasterConfig; -import com.google.gson.JsonSyntaxException; -import com.collectionlogmaster.domain.savedata.BaseSaveData; -import com.collectionlogmaster.domain.savedata.SaveData; -import com.collectionlogmaster.domain.savedata.SaveDataUpdater; -import com.collectionlogmaster.util.EventBusSubscriber; -import com.collectionlogmaster.util.SimpleDebouncer; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameState; -import net.runelite.api.events.GameStateChanged; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.time.Instant; - -import static com.collectionlogmaster.CollectionLogMasterConfig.CONFIG_GROUP; -import static com.collectionlogmaster.util.GsonOverride.GSON; - -@Singleton -@Slf4j -public class SaveDataStorage extends EventBusSubscriber { - public static final String SAVE_DATA_KEY = "save-data"; - - public static final String SAVE_DATA_BACKUP_KEY_BASE = "save-data-bk"; - - @Inject - private ConfigManager configManager; - - @Inject - private SaveDataUpdater saveDataUpdater; - - @Inject - private SimpleDebouncer saveDebouncer; - - private SaveData data; - - @Override - public void startUp() { - super.startUp(); - load(); - } - - @Subscribe - public void onGameStateChanged(GameStateChanged e) { - GameState state = e.getGameState(); - switch (state) { - case LOGGED_IN: - load(); - break; - - case LOGIN_SCREEN: - saveImmediately(); - break; - } - } - - public SaveData get() { - return data; - } - - public void save() { - log.debug("Scheduling save; {}", Instant.now()); - saveDebouncer.debounce(this::saveImmediately); - } - - public void saveImmediately() { - log.debug("Saving; {}", Instant.now()); - String json = GSON.toJson(data); - configManager.setRSProfileConfiguration(CONFIG_GROUP, SAVE_DATA_KEY, json); - } - - public void saveBackup(BaseSaveData data) { - String json = GSON.toJson(data); - configManager.setRSProfileConfiguration( - CONFIG_GROUP, - SAVE_DATA_BACKUP_KEY_BASE + data.getVersion(), - json - ); - } - - private void load() { - importOldPluginSave(); - data = read(); - } - - private @NonNull SaveData read() { - String json = configManager.getRSProfileConfiguration(CONFIG_GROUP, SAVE_DATA_KEY); - if (json == null) { - return new SaveData(); - } - - try { - return saveDataUpdater.update(json); - } catch (JsonSyntaxException e) { - log.error("Unable to parse save data JSON", e); - } - - return new SaveData(); - } - - private void importOldPluginSave() { - Boolean alreadyImported = configManager.getRSProfileConfiguration( - CollectionLogMasterConfig.CONFIG_GROUP, - "oldPluginSaveImported", - Boolean.class - ); - - if (alreadyImported != null && alreadyImported) { - return; - } - - log.info("Importing old plugin save for profile {}", configManager.getRSProfileKey()); - String oldSave = configManager.getRSProfileConfiguration("log-master", SAVE_DATA_KEY); - log.info("Old save: {}", oldSave); - - if (oldSave != null) { - configManager.setRSProfileConfiguration(CONFIG_GROUP, SAVE_DATA_KEY, oldSave); - } - - configManager.setRSProfileConfiguration(CONFIG_GROUP, "oldPluginSaveImported", true); - } -} diff --git a/src/main/java/com/collectionlogmaster/task/TaskListStorage.java b/src/main/java/com/collectionlogmaster/task/TaskListStorage.java deleted file mode 100644 index a71a11ef..00000000 --- a/src/main/java/com/collectionlogmaster/task/TaskListStorage.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.collectionlogmaster.task; - -import com.collectionlogmaster.domain.TieredTaskList; -import com.collectionlogmaster.util.FileUtils; -import com.collectionlogmaster.util.HttpClient; -import java.util.concurrent.CompletableFuture; -import javax.inject.Inject; -import javax.inject.Singleton; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; - - -@Slf4j -@Singleton -public class TaskListStorage { - private static final String LOCAL_TASK_LIST_FILE = "task-list.json"; - - private static final String REMOTE_TASK_LIST_URL = "https://raw.githubusercontent.com/OSRS-Taskman/collection-log-master/refs/heads/main/src/main/resources/com/collectionlogmaster/task-list.json"; - - private final HttpClient httpClient; - - private @NonNull TieredTaskList taskList = new TieredTaskList(); - - @Inject - public TaskListStorage(HttpClient httpClient) { - this.httpClient = httpClient; - loadAsync(); - } - - public @NonNull TieredTaskList get() { - return taskList; - } - - private void loadAsync() { - fetchRemoteAsync() - .exceptionally(t -> fetchLocal()) - .thenAccept(taskList -> this.taskList = taskList); - } - - private @NonNull TieredTaskList fetchLocal() { - return FileUtils.loadResource(LOCAL_TASK_LIST_FILE, TieredTaskList.class); - } - - private CompletableFuture fetchRemoteAsync() { - return httpClient.getHttpRequestAsync(REMOTE_TASK_LIST_URL, TieredTaskList.class); - } -} diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java new file mode 100644 index 00000000..235e5e7c --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java @@ -0,0 +1,64 @@ +package com.collectionlogmaster.taskapp; + +import com.collectionlogmaster.CollectionLogMasterConfig; +import com.collectionlogmaster.taskapp.response.LoginResponse; +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +import org.jetbrains.annotations.NotNull; + +public class TaskAppAuthInterceptor implements Interceptor { + private static final Duration TOKEN_DURATION = Duration.ofHours(12); + + private final TaskAppClient taskAppClient; + + private final CollectionLogMasterConfig config; + + private String jwtToken; + + private Instant tokenExpiresAt; + + public TaskAppAuthInterceptor(TaskAppClient taskAppClient, CollectionLogMasterConfig config) { + this.taskAppClient = taskAppClient; + this.config = config; + } + + private boolean isTokenValid() { + return jwtToken != null + && tokenExpiresAt.isAfter(Instant.now()); + } + + private boolean isLoginRequest(Request req) { + return req.url().encodedPath().endsWith("/login"); + } + + private void authenticate() { + LoginResponse res = taskAppClient.login(config.username(), config.password()).join(); + jwtToken = res.getToken(); + tokenExpiresAt = Instant.now().plus(TOKEN_DURATION); + } + + @Override + public @NotNull Response intercept(@NotNull Chain chain) throws IOException { + Request originalRequest = chain.request(); + + if (isLoginRequest(originalRequest)) { + return chain.proceed(originalRequest); + } + + synchronized (this) { + if (!isTokenValid()) { + authenticate(); + } + } + + Request newRequest = originalRequest.newBuilder() + .header("Authorization", "Bearer " + jwtToken) + .build(); + + return chain.proceed(newRequest); + } +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java new file mode 100644 index 00000000..39e48fbb --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java @@ -0,0 +1,96 @@ +package com.collectionlogmaster.taskapp; + +import com.collectionlogmaster.CollectionLogMasterConfig; +import com.collectionlogmaster.taskapp.response.GenerateTaskResponse; +import com.collectionlogmaster.taskapp.response.LoginResponse; +import com.collectionlogmaster.taskapp.response.TaskListResponse; +import com.collectionlogmaster.taskapp.response.UserProfileResponse; +import com.collectionlogmaster.util.HttpClient; +import com.google.gson.JsonObject; +import java.util.concurrent.CompletableFuture; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import org.jetbrains.annotations.NotNull; + +@Slf4j +@Singleton +public class TaskAppClient extends HttpClient { + private static final HttpUrl DEVELOPMENT_BASE_API_URL = new HttpUrl.Builder() + .scheme("http") + .host("192.168.15.15") + .port(5001) + .addPathSegments("api/v2") + .build(); + + private static final HttpUrl PRODUCTION_BASE_API_URL = new HttpUrl.Builder() + .scheme("https") + .host("www.osrstaskapp.com") + .addPathSegments("api/v2") + .build(); + + @Inject + @Named("developerMode") + private boolean isDeveloperMode; + + @Inject + public TaskAppClient(OkHttpClient okHttpClient, CollectionLogMasterConfig config) { + super(okHttpClient); + + TaskAppAuthInterceptor taskAppAuthInterceptor = new TaskAppAuthInterceptor(this, config); + this.okHttpClient = okHttpClient.newBuilder() + .addInterceptor(taskAppAuthInterceptor) + .build(); + } + + private @NotNull HttpUrl buildApiUrl(String... segments) { + HttpUrl baseApiUrl = isDeveloperMode ? DEVELOPMENT_BASE_API_URL : PRODUCTION_BASE_API_URL; + HttpUrl.Builder builder = baseApiUrl.newBuilder(); + + for (String segment : segments) { + builder.addPathSegments(segment); + } + + return builder.build(); + } + + public CompletableFuture login(String username, String password) { + HttpUrl url = buildApiUrl("login"); + + JsonObject body = new JsonObject(); + body.addProperty("username", username); + body.addProperty("password", password); + + return post(url, body, LoginResponse.class); + } + + public CompletableFuture getTaskList() { + HttpUrl url = buildApiUrl("task-list"); + + return get(url, TaskListResponse.class); + } + + public CompletableFuture getUserProfile() { + HttpUrl url = buildApiUrl("user/profile"); + + return get(url, UserProfileResponse.class); + } + + public CompletableFuture updateTask(String taskId, boolean completed) { + HttpUrl url = buildApiUrl("user/tasks", taskId); + + JsonObject body = new JsonObject(); + body.addProperty("completed", completed); + + return patch(url, body, null); + } + + public CompletableFuture generateTask() { + HttpUrl url = buildApiUrl("user/generate-task"); + + return post(url, GenerateTaskResponse.class); + } +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java new file mode 100644 index 00000000..cdecdbaf --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java @@ -0,0 +1,13 @@ +package com.collectionlogmaster.taskapp; + +import lombok.Data; +import java.util.HashSet; +import java.util.Set; + +@Data +public class TaskAppState { + private String activeTaskId = null; + private boolean isOfficial = false; + private boolean isLmsEnabled = false; + private Set completedTasks = new HashSet<>(); +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java new file mode 100644 index 00000000..cf0f13a2 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java @@ -0,0 +1,52 @@ +package com.collectionlogmaster.taskapp; + +import com.collectionlogmaster.command.TaskmanCommandManager; +import com.collectionlogmaster.taskapp.domain.CompletedTask; +import com.collectionlogmaster.util.EventBusSubscriber; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; + +@Singleton +@Slf4j +public class TaskAppStateStorage extends EventBusSubscriber { + @Inject + private TaskAppClient taskAppClient; + + @Inject + private TaskmanCommandManager taskmanCommandManager; + + private TaskAppState data = new TaskAppState(); + + @Override + public void startUp() { + super.startUp(); + fetch(); + } + + public TaskAppState get() { + return data; + } + + public CompletableFuture fetch() { + int hashBefore = data.hashCode(); + return taskAppClient.getUserProfile() + .thenAccept(res -> { + data.setActiveTaskId(res.getActiveTaskId()); + data.setOfficial(res.isOfficial()); + data.setLmsEnabled(res.isLmsEnabled()); + + Set newCompletedTasks = res.getCompletedTasks().stream() + .map(CompletedTask::getId) + .collect(Collectors.toSet()); + data.setCompletedTasks(newCompletedTasks); + + if (data.hashCode() != hashBefore) { + taskmanCommandManager.updateServer(); + } + }); + } +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java b/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java new file mode 100644 index 00000000..91b16919 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java @@ -0,0 +1,31 @@ +package com.collectionlogmaster.taskapp; + +import com.collectionlogmaster.domain.TieredTaskList; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +@Singleton +public class TaskListStorage { + private final TaskAppClient taskAppClient; + + private @NonNull TieredTaskList taskList = new TieredTaskList(); + + @Inject + public TaskListStorage(TaskAppClient taskAppClient) { + this.taskAppClient = taskAppClient; + loadAsync(); + } + + public @NonNull TieredTaskList get() { + return taskList; + } + + private void loadAsync() { + taskAppClient.getTaskList() + .thenAccept(taskList -> this.taskList = taskList); + } +} diff --git a/src/main/java/com/collectionlogmaster/task/TaskService.java b/src/main/java/com/collectionlogmaster/taskapp/TaskService.java similarity index 53% rename from src/main/java/com/collectionlogmaster/task/TaskService.java rename to src/main/java/com/collectionlogmaster/taskapp/TaskService.java index 8e0ed710..9657b53d 100644 --- a/src/main/java/com/collectionlogmaster/task/TaskService.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskService.java @@ -1,21 +1,19 @@ -package com.collectionlogmaster.task; +package com.collectionlogmaster.taskapp; import com.collectionlogmaster.CollectionLogMasterConfig; -import com.collectionlogmaster.command.TaskmanCommandManager; import com.collectionlogmaster.domain.Tag; import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.domain.savedata.SaveData; -import com.collectionlogmaster.domain.verification.clog.CollectionLogVerification; +import com.collectionlogmaster.taskapp.response.LoginResponse; import com.collectionlogmaster.util.EventBusSubscriber; +import com.collectionlogmaster.util.SimpleDebouncer; import java.util.Arrays; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Singleton; import lombok.NonNull; @@ -31,24 +29,27 @@ public class TaskService extends EventBusSubscriber { private CollectionLogMasterConfig config; @Inject - private SaveDataStorage saveDataStorage; + private TaskAppStateStorage taskAppStateStorage; @Inject private TaskListStorage taskListStorage; @Inject - private TaskmanCommandManager taskmanCommandManager; + private TaskAppClient taskAppClient; + + @Inject + private SimpleDebouncer loginDebouncer; @Override public void startUp() { super.startUp(); - saveDataStorage.startUp(); + taskAppStateStorage.startUp(); } @Override public void shutDown() { super.shutDown(); - saveDataStorage.shutDown(); + taskAppStateStorage.shutDown(); } @Subscribe @@ -57,21 +58,26 @@ public void onConfigChanged(ConfigChanged e) { return; } - if (!e.getKey().equals(CollectionLogMasterConfig.IS_LMS_ENABLED_KEY)) { + String configKey = e.getKey(); + if ( + !configKey.equals(CollectionLogMasterConfig.TASK_APP_USERNAME) + && !configKey.equals(CollectionLogMasterConfig.TASK_APP_PASSWORD) + ) { return; } - if (!config.isLMSEnabled()) { - Task activeTask = getActiveTask(); - if (activeTask != null && activeTask.isLMS()) { - saveDataStorage.get().setActiveTaskId(null); - saveDataStorage.save(); - } - } + // TODO: handle reauth when credentials change +// loginDebouncer.debounce(this::login); } + private CompletableFuture login() { + return taskAppClient.login(config.username(), config.password()); + } + + + public Task getActiveTask() { - String activeTaskId = saveDataStorage.get().getActiveTaskId(); + String activeTaskId = taskAppStateStorage.get().getActiveTaskId(); return activeTaskId == null ? null : getTaskById(activeTaskId); } @@ -110,7 +116,7 @@ public List getTierTasks(TaskTier tier) { public List getTierTasks(TaskTier tier, boolean skipLMSCheck) { List tierTasks = taskListStorage.get().getForTier(tier); - if (!skipLMSCheck && !config.isLMSEnabled()) { + if (!skipLMSCheck && !taskAppStateStorage.get().isLmsEnabled()) { return filterTag(tierTasks, Tag.LMS); } @@ -138,7 +144,7 @@ public List getVisibleTiers() { } public @NonNull Map getProgress() { - SaveData data = saveDataStorage.get(); + TaskAppState data = taskAppStateStorage.get(); Set completedTasks = data.getCompletedTasks(); Map completionPercentages = new HashMap<>(); @@ -158,97 +164,42 @@ public List getVisibleTiers() { return completionPercentages; } - public Task generate() { - SaveData data = saveDataStorage.get(); - - String activeTaskId = data.getActiveTaskId(); - if (activeTaskId != null) { - log.warn("Tried to generate task when previous one wasn't completed yet"); - return null; - } - - List incompleteTierTasks = getIncompleteTierTasks(); - if (incompleteTierTasks.isEmpty()) { - log.warn("No tasks left"); - return null; - } - - Task generatedTask = pickRandomTask(incompleteTierTasks); - log.debug("New task generated: {}", generatedTask); - - data.setActiveTaskId(generatedTask.getId()); - saveDataStorage.save(); - taskmanCommandManager.updateServer(); - - return generatedTask; + public CompletableFuture generate() { + return taskAppClient.generateTask() + .thenCompose((res) -> taskAppStateStorage.fetch()) + .thenApply((v) -> getActiveTask()); } - public void complete() { - Task activeTask = getActiveTask(); - if (activeTask == null) { - return; - } - - complete(activeTask.getId()); + public CompletableFuture complete() { + return complete(taskAppStateStorage.get().getActiveTaskId()); } - public void complete(String taskId) { - SaveData data = saveDataStorage.get(); - Set completedTasks = data.getCompletedTasks(); - completedTasks.add(taskId); - - if (taskId.equals(data.getActiveTaskId())) { - data.setActiveTaskId(null); - } - - saveDataStorage.save(); - taskmanCommandManager.updateServer(); + public CompletableFuture complete(String taskId) { + return taskAppClient.updateTask(taskId, true) + .thenCompose((res) -> taskAppStateStorage.fetch()); } - public void uncomplete(String taskId) { - Set completedTasks = saveDataStorage.get().getCompletedTasks(); - completedTasks.remove(taskId); - - saveDataStorage.save(); - taskmanCommandManager.updateServer(); + public CompletableFuture uncomplete(String taskId) { + return taskAppClient.updateTask(taskId, false) + .thenCompose((res) -> taskAppStateStorage.fetch()); } - public boolean toggleComplete(String taskId) { + public CompletableFuture toggleComplete(String taskId) { if (isComplete(taskId)) { - uncomplete(taskId); - return false; + return uncomplete(taskId) + .thenApply(v -> false); } else { - complete(taskId); - return true; + return complete(taskId) + .thenApply(v -> true); } } public boolean isComplete(String taskId) { - Set completedTasks = saveDataStorage.get().getCompletedTasks(); + Set completedTasks = taskAppStateStorage.get().getCompletedTasks(); return completedTasks.contains(taskId); } - private Task pickRandomTask(List tasks) { - int index = (int) Math.floor(Math.random() * tasks.size()); - Task pickedTask = tasks.get(index); - - if (!(pickedTask.getVerification() instanceof CollectionLogVerification)) { - return pickedTask; - } - - // get first of similarly named tasks - String taskName = pickedTask.getName(); - Stream similarTasks = tasks.stream() - .filter(t -> taskName.equals(t.getName())) - .filter(t -> t.getVerification() instanceof CollectionLogVerification); - - //noinspection DataFlowIssue - return similarTasks.min(Comparator.comparingInt( - t -> ((CollectionLogVerification) t.getVerification()).getCount() - )).orElse(pickedTask); - } - private List filterTag(List list, Tag tag) { return list.stream() .filter(t -> !t.getTags().contains(tag)) diff --git a/src/main/java/com/collectionlogmaster/taskapp/domain/CompletedTask.java b/src/main/java/com/collectionlogmaster/taskapp/domain/CompletedTask.java new file mode 100644 index 00000000..357d4844 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/domain/CompletedTask.java @@ -0,0 +1,14 @@ +package com.collectionlogmaster.taskapp.domain; + +import java.time.Instant; +import java.util.Set; +import lombok.Data; + +@Data +public class CompletedTask { + private final String id; + private final Set completedItemIds; + // TODO: handle the dumb way we serialize dates in taskapp +// private final Instant assignedDate; +// private final Instant completedDate; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/GenerateTaskResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/GenerateTaskResponse.java new file mode 100644 index 00000000..8bc6eca8 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/response/GenerateTaskResponse.java @@ -0,0 +1,8 @@ +package com.collectionlogmaster.taskapp.response; + +import lombok.Data; + +@Data +public class GenerateTaskResponse { + private String taskId; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/LoginResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/LoginResponse.java new file mode 100644 index 00000000..269d8ab0 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/response/LoginResponse.java @@ -0,0 +1,8 @@ +package com.collectionlogmaster.taskapp.response; + +import lombok.Data; + +@Data +public class LoginResponse { + private final String token; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/TaskListResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/TaskListResponse.java new file mode 100644 index 00000000..cd23333b --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/response/TaskListResponse.java @@ -0,0 +1,11 @@ +package com.collectionlogmaster.taskapp.response; + +import com.collectionlogmaster.domain.TieredTaskList; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class TaskListResponse extends TieredTaskList { + +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java new file mode 100644 index 00000000..6d60ace1 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java @@ -0,0 +1,14 @@ +package com.collectionlogmaster.taskapp.response; + +import com.collectionlogmaster.taskapp.domain.CompletedTask; +import java.util.List; +import lombok.Data; + +@Data +public class UserProfileResponse { + private final String username; + private final boolean isOfficial; + private final boolean isLmsEnabled; + private final String activeTaskId; + private final List completedTasks; +} diff --git a/src/main/java/com/collectionlogmaster/ui/TaskOverlay.java b/src/main/java/com/collectionlogmaster/ui/TaskOverlay.java index 6266b5a4..f0537016 100644 --- a/src/main/java/com/collectionlogmaster/ui/TaskOverlay.java +++ b/src/main/java/com/collectionlogmaster/ui/TaskOverlay.java @@ -2,7 +2,7 @@ import com.collectionlogmaster.CollectionLogMasterConfig; import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.state.StateStore; import java.awt.Color; import java.awt.Dimension; diff --git a/src/main/java/com/collectionlogmaster/ui/component/MainTabbedContainer.java b/src/main/java/com/collectionlogmaster/ui/component/MainTabbedContainer.java index 5e085d29..3d3461c3 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/MainTabbedContainer.java +++ b/src/main/java/com/collectionlogmaster/ui/component/MainTabbedContainer.java @@ -2,7 +2,7 @@ import com.collectionlogmaster.CollectionLogMasterPlugin; import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.generic.ScrollAxis; import com.collectionlogmaster.ui.generic.UIComponent; import com.collectionlogmaster.ui.generic.UIScrollableContainer; diff --git a/src/main/java/com/collectionlogmaster/ui/component/TaskComponent.java b/src/main/java/com/collectionlogmaster/ui/component/TaskComponent.java index 3544fae4..a6ea48a5 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/TaskComponent.java +++ b/src/main/java/com/collectionlogmaster/ui/component/TaskComponent.java @@ -2,7 +2,7 @@ import com.collectionlogmaster.CollectionLogMasterPlugin; import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.InterfaceManager; import com.collectionlogmaster.ui.generic.BorderTheme; import com.collectionlogmaster.ui.generic.UIBorderedContainer; @@ -21,6 +21,7 @@ import net.runelite.api.widgets.WidgetSizeMode; import net.runelite.api.widgets.WidgetTextAlignment; import net.runelite.api.widgets.WidgetType; +import net.runelite.client.callback.ClientThread; import org.jetbrains.annotations.Range; @Accessors(chain = true) @@ -40,6 +41,9 @@ public class TaskComponent extends UIComponent { @Inject private InterfaceManager interfaceManager; + @Inject + private ClientThread clientThread; + @Inject private TaskService taskService; @@ -79,11 +83,12 @@ private void onActionSelected(ScriptEvent e) { return; case 2: - boolean isComplete = taskService.toggleComplete(task.getId()); - setOpacity(isComplete ? 0 : 175) - .setTheme(isComplete ? BorderTheme.ETCHED_GREEN_DYED : BorderTheme.ETCHED) - .revalidate(); - return; + taskService.toggleComplete(task.getId()).thenAccept( + (isComplete) -> clientThread.invoke( + () -> setOpacity(isComplete ? 0 : 175) + .setTheme(isComplete ? BorderTheme.ETCHED_GREEN_DYED : BorderTheme.ETCHED) + .revalidate() + )); } } diff --git a/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java b/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java index 7f68fe13..2f72c17f 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java +++ b/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java @@ -5,7 +5,7 @@ import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.TaskTier; import com.collectionlogmaster.synchronization.SyncService; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.generic.UIComponent; import com.collectionlogmaster.ui.generic.UIUtil; import com.collectionlogmaster.ui.generic.button.UIButton.State; @@ -160,28 +160,30 @@ private void initializeWidgets() { } private void generateTask() { - Task generatedTask = taskService.generate(); - List rollTasks = getRollTasks(); + taskService.generate() + .thenAccept(generatedTask -> { + List rollTasks = getRollTasks(); - Stack> stepStack = new Stack<>(); - stepStack.push(Pair.of(generatedTask, 0)); + Stack> stepStack = new Stack<>(); + stepStack.push(Pair.of(generatedTask, 0)); - int timeLeft = config.rollTime(); - while (timeLeft > 0) { - int stepDelay = calculateStepDelay(stepStack.size() - 1); + int timeLeft = config.rollTime(); + while (timeLeft > 0) { + int stepDelay = calculateStepDelay(stepStack.size() - 1); - stepStack.push(Pair.of( - rollTasks.get(stepStack.size()), - stepDelay - )); + stepStack.push(Pair.of( + rollTasks.get(stepStack.size()), + stepDelay + )); - timeLeft -= stepDelay; - } + timeLeft -= stepDelay; + } - executeRollStep(stepStack); + executeRollStep(stepStack); - generateButton.setState(State.DISABLED) - .revalidate(); + generateButton.setState(State.DISABLED) + .revalidate(); + }); } /** @@ -250,10 +252,10 @@ public void revalidate() { if (activeTask != null) { completeButton.setState(State.DEFAULT) .setName(UIUtil.formatName(activeTask.getName())) - .setAction("Complete", () -> { - taskService.complete(); - revalidate(); - }) + .setAction("Complete", () -> + taskService.complete() + .thenRun(() -> clientThread.invoke(this::revalidate)) + ) .revalidate(); generateButton.setState(State.DISABLED) diff --git a/src/main/java/com/collectionlogmaster/ui/component/TaskInfo.java b/src/main/java/com/collectionlogmaster/ui/component/TaskInfo.java index 7f41e235..a9607c77 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/TaskInfo.java +++ b/src/main/java/com/collectionlogmaster/ui/component/TaskInfo.java @@ -10,7 +10,7 @@ import com.collectionlogmaster.domain.verification.skill.SkillVerification; import com.collectionlogmaster.synchronization.clog.CollectionLogService; import com.collectionlogmaster.synchronization.diary.AchievementDiaryService; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.generic.UIComponent; import com.collectionlogmaster.ui.generic.UIGridContainer; import com.collectionlogmaster.ui.generic.UIProgressBar; @@ -34,6 +34,7 @@ import net.runelite.api.widgets.WidgetSizeMode; import net.runelite.api.widgets.WidgetTextAlignment; import net.runelite.api.widgets.WidgetType; +import net.runelite.client.callback.ClientThread; import net.runelite.client.game.ItemManager; import net.runelite.client.util.LinkBrowser; import org.apache.commons.lang3.tuple.Pair; @@ -63,6 +64,9 @@ public class TaskInfo extends UIComponent { @Inject private ItemManager itemManager; + @Inject + private ClientThread clientThread; + @Inject private TaskService taskService; @@ -287,8 +291,8 @@ private void initializeWidgets() { .setFont(FontID.BOLD_12) .setText("Mark Complete") .setAction("Mark", () -> { - taskService.toggleComplete(task.getId()); - revalidate(); + taskService.toggleComplete(task.getId()) + .thenRun(() -> clientThread.invoke(this::revalidate)); }) .revalidate(); } diff --git a/src/main/java/com/collectionlogmaster/ui/component/TaskList.java b/src/main/java/com/collectionlogmaster/ui/component/TaskList.java index ff8250cf..34695489 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/TaskList.java +++ b/src/main/java/com/collectionlogmaster/ui/component/TaskList.java @@ -2,7 +2,7 @@ import com.collectionlogmaster.CollectionLogMasterPlugin; import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.generic.BorderTheme; import com.collectionlogmaster.ui.generic.UIComponent; import com.collectionlogmaster.ui.generic.UIGridContainer; diff --git a/src/main/java/com/collectionlogmaster/util/GsonOverride.java b/src/main/java/com/collectionlogmaster/util/GsonOverride.java index d4d44e5c..24d0a09d 100644 --- a/src/main/java/com/collectionlogmaster/util/GsonOverride.java +++ b/src/main/java/com/collectionlogmaster/util/GsonOverride.java @@ -1,5 +1,6 @@ package com.collectionlogmaster.util; +import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.collectionlogmaster.domain.Tag; @@ -22,6 +23,7 @@ public class GsonOverride { @Inject public GsonOverride(Gson originalGson) { GsonBuilder gsonBuilder = originalGson.newBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeAdapter(Verification.class, new VerificationAdapter()) .registerTypeAdapter(VerificationMethod.class, new EnumAdapter<>(VerificationMethod.class)) .registerTypeAdapter(DiaryRegion.class, new EnumAdapter<>(DiaryRegion.class)) diff --git a/src/main/java/com/collectionlogmaster/util/HttpClient.java b/src/main/java/com/collectionlogmaster/util/HttpClient.java index e4f5c95f..5babef8e 100644 --- a/src/main/java/com/collectionlogmaster/util/HttpClient.java +++ b/src/main/java/com/collectionlogmaster/util/HttpClient.java @@ -1,6 +1,7 @@ package com.collectionlogmaster.util; import com.collectionlogmaster.PluginUpdateNotifier; +import com.google.gson.JsonObject; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import net.runelite.client.RuneLiteProperties; @@ -9,7 +10,6 @@ import javax.annotation.Nullable; import javax.inject.Inject; import java.io.IOException; -import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @@ -17,37 +17,40 @@ @Slf4j public class HttpClient { - private static final MediaType JSON_MEDIA_TYPE = Objects.requireNonNull(MediaType.parse("application/json; charset=utf-8")); + private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8"); - @Inject - private OkHttpClient okHttpClient; + protected OkHttpClient okHttpClient; private final String userAgent; @Inject - public HttpClient() { + public HttpClient(OkHttpClient okHttpClient) { + this.okHttpClient = okHttpClient; + String runeliteVersion = RuneLiteProperties.getVersion(); String pluginVersion = PluginUpdateNotifier.getPluginVersion(); - userAgent = "RuneLite:" + runeliteVersion + "," + "CLogMaster:" + pluginVersion; + userAgent = "RuneLite: " + runeliteVersion + ", CLogMaster: " + pluginVersion; } - private Request.Builder buildRequest(String url, Consumer methodSetter) { + protected Request.Builder buildRequest(HttpUrl url, Consumer methodSetter) { Request.Builder builder = new Request.Builder() .url(url) .header("Content-Type", "application/json") .header("User-Agent", userAgent); + methodSetter.accept(builder); + return builder; } - private CompletableFuture executeHttpRequestAsync(Request request) { + protected CompletableFuture executeRequest(Request request) { log.debug("Sending {} request to {}; data = {}", request.method(), request.url(), request.body()); CompletableFuture future = new CompletableFuture<>(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { - log.warn("Async request failed.", e); + log.error("Async request failed.", e); future.completeExceptionally(e); } @@ -59,28 +62,42 @@ public void onResponse(@NonNull Call call, @NonNull Response response) { return future; } - public CompletableFuture postHttpRequestAsync(String url, String data, @Nullable Class clazz) { - RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, data); + public CompletableFuture get(HttpUrl url, @Nullable Class clazz) { + Request request = buildRequest(url, Request.Builder::get).build(); + + return executeRequest(request) + .thenApply((response) -> parseResponse(response, clazz)); + } + + public CompletableFuture post(HttpUrl url, @Nullable Class clazz) { + return post(url, new JsonObject(), clazz); + } + + public CompletableFuture post(HttpUrl url, JsonObject json, @Nullable Class clazz) { + RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, json.toString()); Request request = buildRequest(url, builder -> builder.post(body)).build(); - return executeHttpRequestAsync(request) - .thenApply((response) -> handleResponse(response, clazz)); + + return executeRequest(request) + .thenApply((response) -> parseResponse(response, clazz)); } - public CompletableFuture putHttpRequestAsync(String url, String data, @Nullable Class clazz) { - RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, data); + public CompletableFuture put(HttpUrl url, String jsonBody, @Nullable Class clazz) { + RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, jsonBody); Request request = buildRequest(url, builder -> builder.put(body)).build(); - return executeHttpRequestAsync(request) - .thenApply((response) -> handleResponse(response, clazz)); + + return executeRequest(request) + .thenApply((response) -> parseResponse(response, clazz)); } - public CompletableFuture getHttpRequestAsync(String url, @Nullable Class clazz) { - Request request = buildRequest(url, Request.Builder::get) - .build(); - return executeHttpRequestAsync(request) - .thenApply((response) -> handleResponse(response, clazz)); + public CompletableFuture patch(HttpUrl url, JsonObject json, @Nullable Class clazz) { + RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, json.toString()); + Request request = buildRequest(url, builder -> builder.patch(body)).build(); + + return executeRequest(request) + .thenApply((response) -> parseResponse(response, clazz)); } - private T handleResponse(Response response, @Nullable Class clazz) { + protected T parseResponse(Response response, @Nullable Class clazz) { try (Response res = response) { ResponseBody body = res.body(); @@ -99,7 +116,8 @@ private T handleResponse(Response response, @Nullable Class clazz) { return GSON.fromJson(bodyString, clazz); } catch (IOException e) { - throw new RuntimeException("Error reading response body"); + log.error("Failed to parse response body.", e); + throw new RuntimeException("Failed to parse response body", e); } } } From dfad9df3f1e46ce2249e64bfb08da6f6191e9475 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Sat, 16 May 2026 17:06:14 -0300 Subject: [PATCH 05/23] invalid auth token on credentials change --- .../taskapp/TaskAppAuthInterceptor.java | 10 +++++++-- .../taskapp/TaskAppClient.java | 8 ++++++- .../taskapp/TaskListStorage.java | 4 ++-- .../taskapp/TaskService.java | 22 +++++-------------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java index 235e5e7c..e9a67216 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java @@ -17,9 +17,9 @@ public class TaskAppAuthInterceptor implements Interceptor { private final CollectionLogMasterConfig config; - private String jwtToken; + private volatile String jwtToken; - private Instant tokenExpiresAt; + private volatile Instant tokenExpiresAt; public TaskAppAuthInterceptor(TaskAppClient taskAppClient, CollectionLogMasterConfig config) { this.taskAppClient = taskAppClient; @@ -31,11 +31,17 @@ private boolean isTokenValid() { && tokenExpiresAt.isAfter(Instant.now()); } + public void invalidateToken() { + jwtToken = null; + tokenExpiresAt = null; + } + private boolean isLoginRequest(Request req) { return req.url().encodedPath().endsWith("/login"); } private void authenticate() { + // it's fine to block here because this executes in the same thread as the request LoginResponse res = taskAppClient.login(config.username(), config.password()).join(); jwtToken = res.getToken(); tokenExpiresAt = Instant.now().plus(TOKEN_DURATION); diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java index 39e48fbb..fae9d3a9 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java @@ -36,11 +36,13 @@ public class TaskAppClient extends HttpClient { @Named("developerMode") private boolean isDeveloperMode; + private final TaskAppAuthInterceptor taskAppAuthInterceptor; + @Inject public TaskAppClient(OkHttpClient okHttpClient, CollectionLogMasterConfig config) { super(okHttpClient); - TaskAppAuthInterceptor taskAppAuthInterceptor = new TaskAppAuthInterceptor(this, config); + taskAppAuthInterceptor = new TaskAppAuthInterceptor(this, config); this.okHttpClient = okHttpClient.newBuilder() .addInterceptor(taskAppAuthInterceptor) .build(); @@ -57,6 +59,10 @@ public TaskAppClient(OkHttpClient okHttpClient, CollectionLogMasterConfig config return builder.build(); } + public void invalidateToken() { + taskAppAuthInterceptor.invalidateToken(); + } + public CompletableFuture login(String username, String password) { HttpUrl url = buildApiUrl("login"); diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java b/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java index 91b16919..9059c973 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java @@ -17,14 +17,14 @@ public class TaskListStorage { @Inject public TaskListStorage(TaskAppClient taskAppClient) { this.taskAppClient = taskAppClient; - loadAsync(); + fetch(); } public @NonNull TieredTaskList get() { return taskList; } - private void loadAsync() { + public void fetch() { taskAppClient.getTaskList() .thenAccept(taskList -> this.taskList = taskList); } diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskService.java b/src/main/java/com/collectionlogmaster/taskapp/TaskService.java index 9657b53d..fbae1797 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskService.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskService.java @@ -4,9 +4,7 @@ import com.collectionlogmaster.domain.Tag; import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.taskapp.response.LoginResponse; import com.collectionlogmaster.util.EventBusSubscriber; -import com.collectionlogmaster.util.SimpleDebouncer; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -37,9 +35,6 @@ public class TaskService extends EventBusSubscriber { @Inject private TaskAppClient taskAppClient; - @Inject - private SimpleDebouncer loginDebouncer; - @Override public void startUp() { super.startUp(); @@ -60,22 +55,15 @@ public void onConfigChanged(ConfigChanged e) { String configKey = e.getKey(); if ( - !configKey.equals(CollectionLogMasterConfig.TASK_APP_USERNAME) - && !configKey.equals(CollectionLogMasterConfig.TASK_APP_PASSWORD) + configKey.equals(CollectionLogMasterConfig.TASK_APP_USERNAME) + || configKey.equals(CollectionLogMasterConfig.TASK_APP_PASSWORD) ) { - return; + taskAppClient.invalidateToken(); + taskAppStateStorage.fetch(); + taskListStorage.fetch(); } - - // TODO: handle reauth when credentials change -// loginDebouncer.debounce(this::login); } - private CompletableFuture login() { - return taskAppClient.login(config.username(), config.password()); - } - - - public Task getActiveTask() { String activeTaskId = taskAppStateStorage.get().getActiveTaskId(); From bcddc01094da4fff3e378fae7adf1892baf914fc Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Sat, 16 May 2026 18:50:15 -0300 Subject: [PATCH 06/23] remove dev commands as they no longer work --- .../CollectionLogMasterPlugin.java | 6 -- .../command/DevCommandsManager.java | 58 ------------------- 2 files changed, 64 deletions(-) delete mode 100644 src/main/java/com/collectionlogmaster/command/DevCommandsManager.java diff --git a/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java b/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java index 15444abd..9d5447fe 100644 --- a/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java +++ b/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java @@ -1,6 +1,5 @@ package com.collectionlogmaster; -import com.collectionlogmaster.command.DevCommandsManager; import com.collectionlogmaster.command.TaskmanCommandManager; import com.collectionlogmaster.input.MouseManager; import com.collectionlogmaster.synchronization.clog.CollectionLogService; @@ -54,9 +53,6 @@ public class CollectionLogMasterPlugin extends Plugin { @Inject public TaskmanCommandManager taskmanCommand; - @Inject - public DevCommandsManager devCommands; - @Override protected void startUp() { CollectionLogMasterPlugin.staticInjector = getInjector(); @@ -67,7 +63,6 @@ protected void startUp() { pluginUpdateNotifier.startUp(); interfaceManager.startUp(); taskmanCommand.startUp(); - devCommands.startUp(); this.taskOverlay.setResizable(true); this.overlayManager.add(this.taskOverlay); } @@ -80,7 +75,6 @@ protected void shutDown() { pluginUpdateNotifier.shutDown(); interfaceManager.shutDown(); taskmanCommand.shutDown(); - devCommands.shutDown(); this.overlayManager.remove(this.taskOverlay); } diff --git a/src/main/java/com/collectionlogmaster/command/DevCommandsManager.java b/src/main/java/com/collectionlogmaster/command/DevCommandsManager.java deleted file mode 100644 index 87ccd99a..00000000 --- a/src/main/java/com/collectionlogmaster/command/DevCommandsManager.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.collectionlogmaster.command; - - -import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.taskapp.TaskAppStateStorage; -import com.collectionlogmaster.taskapp.TaskListStorage; -import com.collectionlogmaster.util.EventBusSubscriber; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.events.CommandExecuted; -import net.runelite.client.eventbus.Subscribe; - -@Slf4j -@Singleton -public class DevCommandsManager extends EventBusSubscriber { - private final String SET_ACTIVE_TASK_COMMAND = "set-active-task"; - - @Inject - private TaskAppStateStorage taskAppStateStorage; - - @Inject - private TaskListStorage taskListStorage; - - @Inject - @Named("developerMode") - private boolean isDeveloperMode; - - @Subscribe - public void onCommandExecuted(CommandExecuted e) { - if (!isDeveloperMode) return; - - String command = e.getCommand(); - String[] args = e.getArguments(); - - log.debug("Command executed: ::{} {}", command, args); - if (command.equals(SET_ACTIVE_TASK_COMMAND)) { - executeSecActiveTaskCommand(args); - } - } - - private void executeSecActiveTaskCommand(String[] args) { - if (args.length != 1) return; - String taskPrefix = args[0]; - - for (Task task : taskListStorage.get().all()) { - String taskId = task.getId(); - if (taskId.startsWith(taskPrefix)) { - log.debug("Setting active task to {}", taskId); - taskAppStateStorage.get().setActiveTaskId(taskId); - return; - } - } - - log.debug("Unable to find task with prefix {}", taskPrefix); - } -} From 289280776b04d52047b48bcc2079409f3a9265c1 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Sat, 16 May 2026 18:59:02 -0300 Subject: [PATCH 07/23] logged failed attempt to load task list --- .../com/collectionlogmaster/taskapp/TaskListStorage.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java b/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java index 9059c973..5c457776 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java @@ -26,6 +26,10 @@ public TaskListStorage(TaskAppClient taskAppClient) { public void fetch() { taskAppClient.getTaskList() - .thenAccept(taskList -> this.taskList = taskList); + .thenAccept(taskList -> this.taskList = taskList) + .exceptionally(e -> { + log.error("Failed to load task list", e); + return null; + }); } } From e84e37c0213b176184cf44f76247b34755012946 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Sat, 16 May 2026 19:17:15 -0300 Subject: [PATCH 08/23] make task app state immutable and improve thread safety --- .../taskapp/TaskAppState.java | 15 ++++++---- .../taskapp/TaskAppStateStorage.java | 28 +++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java index cdecdbaf..2077cad2 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java @@ -1,13 +1,18 @@ package com.collectionlogmaster.taskapp; import lombok.Data; -import java.util.HashSet; import java.util.Set; +import lombok.RequiredArgsConstructor; @Data +@RequiredArgsConstructor public class TaskAppState { - private String activeTaskId = null; - private boolean isOfficial = false; - private boolean isLmsEnabled = false; - private Set completedTasks = new HashSet<>(); + private final String activeTaskId; + private final boolean isOfficial; + private final boolean isLmsEnabled; + private final Set completedTasks; + + public TaskAppState() { + this(null, true, true, Set.of()); + } } diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java index cf0f13a2..68fbefd9 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java @@ -19,7 +19,7 @@ public class TaskAppStateStorage extends EventBusSubscriber { @Inject private TaskmanCommandManager taskmanCommandManager; - private TaskAppState data = new TaskAppState(); + private volatile TaskAppState state = new TaskAppState(); @Override public void startUp() { @@ -28,25 +28,29 @@ public void startUp() { } public TaskAppState get() { - return data; + return state; } public CompletableFuture fetch() { - int hashBefore = data.hashCode(); return taskAppClient.getUserProfile() .thenAccept(res -> { - data.setActiveTaskId(res.getActiveTaskId()); - data.setOfficial(res.isOfficial()); - data.setLmsEnabled(res.isLmsEnabled()); - - Set newCompletedTasks = res.getCompletedTasks().stream() + Set completedTasks = res.getCompletedTasks().stream() .map(CompletedTask::getId) - .collect(Collectors.toSet()); - data.setCompletedTasks(newCompletedTasks); + .collect(Collectors.toUnmodifiableSet()); + + TaskAppState newState = new TaskAppState( + res.getActiveTaskId(), + res.isOfficial(), + res.isLmsEnabled(), + completedTasks + ); - if (data.hashCode() != hashBefore) { - taskmanCommandManager.updateServer(); + if (state.equals(newState)) { + return; } + + state = newState; + taskmanCommandManager.updateServer(); }); } } From 688461d591a4ab588e3fbe4cd170184c6375391b Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Sat, 16 May 2026 19:24:13 -0300 Subject: [PATCH 09/23] point to `127.0.0.1` for local development Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../java/com/collectionlogmaster/taskapp/TaskAppClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java index fae9d3a9..0ccceb67 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java @@ -21,7 +21,7 @@ public class TaskAppClient extends HttpClient { private static final HttpUrl DEVELOPMENT_BASE_API_URL = new HttpUrl.Builder() .scheme("http") - .host("192.168.15.15") + .host("127.0.0.1") .port(5001) .addPathSegments("api/v2") .build(); From 357990f9d42965df93e85ee37ce9a80755954d61 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Sat, 16 May 2026 19:27:37 -0300 Subject: [PATCH 10/23] set invalid token expiry date to Instant.MIN --- .../collectionlogmaster/taskapp/TaskAppAuthInterceptor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java index e9a67216..ff9f3c62 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java @@ -17,9 +17,9 @@ public class TaskAppAuthInterceptor implements Interceptor { private final CollectionLogMasterConfig config; - private volatile String jwtToken; + private volatile String jwtToken = null; - private volatile Instant tokenExpiresAt; + private volatile Instant tokenExpiresAt = Instant.MIN; public TaskAppAuthInterceptor(TaskAppClient taskAppClient, CollectionLogMasterConfig config) { this.taskAppClient = taskAppClient; @@ -33,7 +33,7 @@ private boolean isTokenValid() { public void invalidateToken() { jwtToken = null; - tokenExpiresAt = null; + tokenExpiresAt = Instant.MIN; } private boolean isLoginRequest(Request req) { From e377b8df4aa89e01a9172e6ff19152cfe8ff1994 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Sun, 17 May 2026 00:07:55 -0300 Subject: [PATCH 11/23] use dedicate request classes for request bodies --- .../command/TaskmanCommandManager.java | 2 +- .../taskapp/TaskAppClient.java | 15 ++++++--------- .../taskapp/request/LoginRequest.java | 9 +++++++++ .../taskapp/request/UpdateTaskRequest.java | 8 ++++++++ .../com/collectionlogmaster/util/HttpClient.java | 12 ++++++------ 5 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/collectionlogmaster/taskapp/request/LoginRequest.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/request/UpdateTaskRequest.java diff --git a/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java b/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java index bcc6f6b0..934be0b8 100644 --- a/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java +++ b/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java @@ -146,7 +146,7 @@ public void updateServerImmediately() { CommandRequest data = new CommandRequest(taskId, taskService.getCurrentTier().displayName, (int) currentProgress); - httpClient.put(url, GSON.toJson(data), null); + httpClient.put(url, data, null); } private void replaceChatMessage(ChatMessage chatMessage, CommandResponse res) { diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java index 0ccceb67..d11643d2 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java @@ -1,6 +1,8 @@ package com.collectionlogmaster.taskapp; import com.collectionlogmaster.CollectionLogMasterConfig; +import com.collectionlogmaster.taskapp.request.LoginRequest; +import com.collectionlogmaster.taskapp.request.UpdateTaskRequest; import com.collectionlogmaster.taskapp.response.GenerateTaskResponse; import com.collectionlogmaster.taskapp.response.LoginResponse; import com.collectionlogmaster.taskapp.response.TaskListResponse; @@ -65,12 +67,9 @@ public void invalidateToken() { public CompletableFuture login(String username, String password) { HttpUrl url = buildApiUrl("login"); + LoginRequest data = new LoginRequest(username, password); - JsonObject body = new JsonObject(); - body.addProperty("username", username); - body.addProperty("password", password); - - return post(url, body, LoginResponse.class); + return post(url, data, LoginResponse.class); } public CompletableFuture getTaskList() { @@ -87,11 +86,9 @@ public CompletableFuture getUserProfile() { public CompletableFuture updateTask(String taskId, boolean completed) { HttpUrl url = buildApiUrl("user/tasks", taskId); + UpdateTaskRequest data = new UpdateTaskRequest(completed); - JsonObject body = new JsonObject(); - body.addProperty("completed", completed); - - return patch(url, body, null); + return patch(url, data, null); } public CompletableFuture generateTask() { diff --git a/src/main/java/com/collectionlogmaster/taskapp/request/LoginRequest.java b/src/main/java/com/collectionlogmaster/taskapp/request/LoginRequest.java new file mode 100644 index 00000000..98e1f3a4 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/request/LoginRequest.java @@ -0,0 +1,9 @@ +package com.collectionlogmaster.taskapp.request; + +import lombok.Data; + +@Data +public class LoginRequest { + private final String username; + private final String password; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/request/UpdateTaskRequest.java b/src/main/java/com/collectionlogmaster/taskapp/request/UpdateTaskRequest.java new file mode 100644 index 00000000..76a91f3e --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/request/UpdateTaskRequest.java @@ -0,0 +1,8 @@ +package com.collectionlogmaster.taskapp.request; + +import lombok.Data; + +@Data +public class UpdateTaskRequest { + private final boolean completed; +} diff --git a/src/main/java/com/collectionlogmaster/util/HttpClient.java b/src/main/java/com/collectionlogmaster/util/HttpClient.java index 5babef8e..20c8913d 100644 --- a/src/main/java/com/collectionlogmaster/util/HttpClient.java +++ b/src/main/java/com/collectionlogmaster/util/HttpClient.java @@ -73,24 +73,24 @@ public CompletableFuture post(HttpUrl url, @Nullable Class clazz) { return post(url, new JsonObject(), clazz); } - public CompletableFuture post(HttpUrl url, JsonObject json, @Nullable Class clazz) { - RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, json.toString()); + public CompletableFuture post(HttpUrl url, Object data, @Nullable Class clazz) { + RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, GSON.toJson(data)); Request request = buildRequest(url, builder -> builder.post(body)).build(); return executeRequest(request) .thenApply((response) -> parseResponse(response, clazz)); } - public CompletableFuture put(HttpUrl url, String jsonBody, @Nullable Class clazz) { - RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, jsonBody); + public CompletableFuture put(HttpUrl url, Object data, @Nullable Class clazz) { + RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, GSON.toJson(data)); Request request = buildRequest(url, builder -> builder.put(body)).build(); return executeRequest(request) .thenApply((response) -> parseResponse(response, clazz)); } - public CompletableFuture patch(HttpUrl url, JsonObject json, @Nullable Class clazz) { - RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, json.toString()); + public CompletableFuture patch(HttpUrl url, Object data, @Nullable Class clazz) { + RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, GSON.toJson(data)); Request request = buildRequest(url, builder -> builder.patch(body)).build(); return executeRequest(request) From 10d234f0eb403625b120e99bf4cf6f9420cecaa2 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Sun, 17 May 2026 00:42:37 -0300 Subject: [PATCH 12/23] fix request/response serialization after new field naming strategy --- .../com/collectionlogmaster/domain/command/CommandRequest.java | 3 +++ .../collectionlogmaster/domain/command/CommandResponse.java | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/main/java/com/collectionlogmaster/domain/command/CommandRequest.java b/src/main/java/com/collectionlogmaster/domain/command/CommandRequest.java index 041923bd..c5f149a4 100644 --- a/src/main/java/com/collectionlogmaster/domain/command/CommandRequest.java +++ b/src/main/java/com/collectionlogmaster/domain/command/CommandRequest.java @@ -1,12 +1,15 @@ package com.collectionlogmaster.domain.command; +import com.google.gson.annotations.SerializedName; import lombok.Data; import lombok.RequiredArgsConstructor; @Data @RequiredArgsConstructor public class CommandRequest { + @SerializedName("taskId") private final String taskId; private final String tier; + @SerializedName("progressPercentage") private final int progressPercentage; } diff --git a/src/main/java/com/collectionlogmaster/domain/command/CommandResponse.java b/src/main/java/com/collectionlogmaster/domain/command/CommandResponse.java index 7d406b98..c6bbecfa 100644 --- a/src/main/java/com/collectionlogmaster/domain/command/CommandResponse.java +++ b/src/main/java/com/collectionlogmaster/domain/command/CommandResponse.java @@ -1,10 +1,12 @@ package com.collectionlogmaster.domain.command; +import com.google.gson.annotations.SerializedName; import lombok.Data; @Data public class CommandResponse { private CommandTask task; private String tier; + @SerializedName("progressPercentage") private int progressPercentage; } From b320bcb0f74296da67c71ffaa791e77ccafedbeb Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Sun, 17 May 2026 01:58:52 -0300 Subject: [PATCH 13/23] skip auth on task-list request --- .../taskapp/TaskAppAuthInterceptor.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java index ff9f3c62..70c3babd 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java @@ -36,8 +36,10 @@ public void invalidateToken() { tokenExpiresAt = Instant.MIN; } - private boolean isLoginRequest(Request req) { - return req.url().encodedPath().endsWith("/login"); + private boolean requestSkipsAuth(Request req) { + String path = req.url().encodedPath(); + return path.endsWith("/login") + || path.endsWith("/task-list"); } private void authenticate() { @@ -51,7 +53,7 @@ private void authenticate() { public @NotNull Response intercept(@NotNull Chain chain) throws IOException { Request originalRequest = chain.request(); - if (isLoginRequest(originalRequest)) { + if (requestSkipsAuth(originalRequest)) { return chain.proceed(originalRequest); } From a736f61c98f44787bd264dceee486ee8007984b5 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Sun, 17 May 2026 19:28:26 -0300 Subject: [PATCH 14/23] reimplement sync functionality through task app --- .../synchronization/SyncService.java | 66 +++++++++++-------- .../synchronization/Verifier.java | 3 +- .../clog/CollectionLogService.java | 4 ++ .../clog/CollectionLogVerifier.java | 7 +- .../diary/AchievementDiaryVerifier.java | 25 ++++++- .../synchronization/skill/SkillVerifier.java | 15 ++++- .../taskapp/TaskAppClient.java | 19 +++++- .../taskapp/request/SyncRequest.java | 15 +++++ .../taskapp/response/SyncResponse.java | 10 +++ .../util/GsonOverride.java | 1 + 10 files changed, 130 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/collectionlogmaster/taskapp/request/SyncRequest.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/response/SyncResponse.java diff --git a/src/main/java/com/collectionlogmaster/synchronization/SyncService.java b/src/main/java/com/collectionlogmaster/synchronization/SyncService.java index ca786a36..0c0992bb 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/SyncService.java +++ b/src/main/java/com/collectionlogmaster/synchronization/SyncService.java @@ -5,13 +5,18 @@ import com.collectionlogmaster.synchronization.clog.CollectionLogVerifier; import com.collectionlogmaster.synchronization.diary.AchievementDiaryVerifier; import com.collectionlogmaster.synchronization.skill.SkillVerifier; +import com.collectionlogmaster.taskapp.TaskAppClient; +import com.collectionlogmaster.taskapp.TaskAppStateStorage; import com.collectionlogmaster.taskapp.TaskService; +import java.util.ArrayList; +import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; +import net.runelite.client.callback.ClientThread; @Slf4j @Singleton @@ -19,11 +24,17 @@ public class SyncService { @Inject private Client client; + @Inject + private ClientThread clientThread; + @Inject private TaskService taskService; @Inject - private SyncService syncService; + private TaskAppStateStorage taskAppStateStorage; + + @Inject + private TaskAppClient taskAppClient; @Inject private CollectionLogVerifier collectionLogVerifier; @@ -53,32 +64,31 @@ private Boolean verify(Task task) { } public void sync() { - // TODO: implement syncing - client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Sync disabled", null); -// int updatedCount = 0; -// for (TaskTier tier : TaskTier.values()) { -// for (Task task : taskService.getTierTasks(tier)) { -// Boolean isVerified = syncService.verify(task); -// if (isVerified == null) { -// continue; -// } -// -// boolean taskChanged = isVerified != taskService.isComplete(task.getId()); -// if (!taskChanged) { -// continue; -// } -// -// taskService.toggleComplete(task.getId()); -// -// String newStatus = isVerified ? "complete" : "incomplete"; -// String msg = String.format("%s tier task '%s' marked as %s", tier.displayName, task.getName(), newStatus); -// client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, ""); -// -// updatedCount++; -// } -// } -// -// String msg = String.format("Task synchronization finalized; %d tasks updated", updatedCount); -// client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, null); + taskAppClient.sync( + collectionLogVerifier.verificationData(), + achievementDiaryVerifier.verificationData(), + skillVerifier.verificationData() + ).thenAccept(res -> { + int updatedCount = res.getCompleted().size() + res.getUncompleted().size(); + List messages = new ArrayList<>(updatedCount); + + for (String taskId : res.getCompleted()) { + Task task = taskService.getTaskById(taskId); + messages.add(String.format("Task '%s' marked as complete", task.getName())); + } + + for (String taskId : res.getUncompleted()) { + Task task = taskService.getTaskById(taskId); + messages.add(String.format("Task '%s' marked as incomplete", task.getName())); + } + + messages.add(String.format("Task synchronization finalized; %d tasks updated", updatedCount)); + + clientThread.invoke(() -> { + for (String msg : messages) { + client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, null); + } + }); + }).thenAccept((v) -> taskAppStateStorage.fetch()); } } diff --git a/src/main/java/com/collectionlogmaster/synchronization/Verifier.java b/src/main/java/com/collectionlogmaster/synchronization/Verifier.java index e30f10f2..068595ba 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/Verifier.java +++ b/src/main/java/com/collectionlogmaster/synchronization/Verifier.java @@ -3,7 +3,8 @@ import com.collectionlogmaster.domain.Task; import lombok.NonNull; -public interface Verifier { +public interface Verifier { boolean supports(@NonNull Task task); boolean verify(@NonNull Task task); + T verificationData(); } diff --git a/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogService.java b/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogService.java index e0f5dc62..910ff985 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogService.java +++ b/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogService.java @@ -39,6 +39,10 @@ public void onGameStateChanged(GameStateChanged gameStateChanged) { } } + public Set getObtainedItems() { + return Set.copyOf(obtainedItems); + } + public boolean isItemObtained(int itemId) { return obtainedItems.contains(itemId); } diff --git a/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogVerifier.java b/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogVerifier.java index 49504b6d..802730a5 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogVerifier.java +++ b/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogVerifier.java @@ -3,6 +3,7 @@ import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.verification.clog.CollectionLogVerification; import com.collectionlogmaster.synchronization.Verifier; +import java.util.Set; import lombok.NonNull; import javax.inject.Inject; @@ -10,7 +11,7 @@ import java.util.Arrays; @Singleton -public class CollectionLogVerifier implements Verifier { +public class CollectionLogVerifier implements Verifier> { @Inject private CollectionLogService collectionLogService; @@ -28,4 +29,8 @@ public boolean verify(@NonNull Task task) { return totalObtained >= verif.getCount(); } + + public Set verificationData() { + return collectionLogService.getObtainedItems(); + } } diff --git a/src/main/java/com/collectionlogmaster/synchronization/diary/AchievementDiaryVerifier.java b/src/main/java/com/collectionlogmaster/synchronization/diary/AchievementDiaryVerifier.java index c5e1ce08..931d8fc2 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/diary/AchievementDiaryVerifier.java +++ b/src/main/java/com/collectionlogmaster/synchronization/diary/AchievementDiaryVerifier.java @@ -5,13 +5,16 @@ import com.collectionlogmaster.domain.verification.diary.DiaryDifficulty; import com.collectionlogmaster.domain.verification.diary.DiaryRegion; import com.collectionlogmaster.synchronization.Verifier; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; import lombok.NonNull; import javax.inject.Inject; import javax.inject.Singleton; @Singleton -public class AchievementDiaryVerifier implements Verifier { +public class AchievementDiaryVerifier implements Verifier>> { @Inject private AchievementDiaryService achievementDiaryService; @@ -23,9 +26,25 @@ public boolean verify(@NonNull Task task) { assert task.getVerification() instanceof AchievementDiaryVerification; AchievementDiaryVerification verif = (AchievementDiaryVerification) task.getVerification(); - DiaryRegion diary = verif.getRegion(); + DiaryRegion region = verif.getRegion(); DiaryDifficulty difficulty = verif.getDifficulty(); - return achievementDiaryService.isComplete(diary, difficulty); + return achievementDiaryService.isComplete(region, difficulty); + } + + public Map> verificationData() { + return Arrays.stream(DiaryRegion.values()) + .collect(Collectors.toMap( + region -> region, + this::verificationData + )); + } + + private Map verificationData(DiaryRegion region) { + return Arrays.stream(DiaryDifficulty.values()) + .collect(Collectors.toMap( + difficulty -> difficulty, + difficulty -> achievementDiaryService.isComplete(region, difficulty) + )); } } diff --git a/src/main/java/com/collectionlogmaster/synchronization/skill/SkillVerifier.java b/src/main/java/com/collectionlogmaster/synchronization/skill/SkillVerifier.java index 2616c8f2..9b8de299 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/skill/SkillVerifier.java +++ b/src/main/java/com/collectionlogmaster/synchronization/skill/SkillVerifier.java @@ -3,14 +3,19 @@ import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.verification.skill.SkillVerification; import com.collectionlogmaster.synchronization.Verifier; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import lombok.NonNull; import net.runelite.api.Client; import javax.inject.Inject; import javax.inject.Singleton; +import net.runelite.api.Skill; @Singleton -public class SkillVerifier implements Verifier { +public class SkillVerifier implements Verifier> { @Inject private Client client; @@ -29,4 +34,12 @@ public boolean verify(@NonNull Task task) { return totalAchieved >= verif.getCount(); } + + public Map verificationData() { + return Arrays.stream(Skill.values()) + .collect(Collectors.toMap( + skill -> skill, + skill -> client.getSkillExperience(skill) + )); + } } diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java index d11643d2..673ffb46 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java @@ -1,19 +1,25 @@ package com.collectionlogmaster.taskapp; import com.collectionlogmaster.CollectionLogMasterConfig; +import com.collectionlogmaster.domain.verification.diary.DiaryDifficulty; +import com.collectionlogmaster.domain.verification.diary.DiaryRegion; +import com.collectionlogmaster.taskapp.request.SyncRequest; import com.collectionlogmaster.taskapp.request.LoginRequest; import com.collectionlogmaster.taskapp.request.UpdateTaskRequest; import com.collectionlogmaster.taskapp.response.GenerateTaskResponse; import com.collectionlogmaster.taskapp.response.LoginResponse; +import com.collectionlogmaster.taskapp.response.SyncResponse; import com.collectionlogmaster.taskapp.response.TaskListResponse; import com.collectionlogmaster.taskapp.response.UserProfileResponse; import com.collectionlogmaster.util.HttpClient; -import com.google.gson.JsonObject; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletableFuture; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Skill; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import org.jetbrains.annotations.NotNull; @@ -96,4 +102,15 @@ public CompletableFuture generateTask() { return post(url, GenerateTaskResponse.class); } + + public CompletableFuture sync( + Set collectionLog, + Map> diaries, + Map skills + ) { + HttpUrl url = buildApiUrl("user/sync"); + SyncRequest data = new SyncRequest(collectionLog, diaries, skills); + + return post(url, data, SyncResponse.class); + } } diff --git a/src/main/java/com/collectionlogmaster/taskapp/request/SyncRequest.java b/src/main/java/com/collectionlogmaster/taskapp/request/SyncRequest.java new file mode 100644 index 00000000..afa7dbc4 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/request/SyncRequest.java @@ -0,0 +1,15 @@ +package com.collectionlogmaster.taskapp.request; + +import com.collectionlogmaster.domain.verification.diary.DiaryDifficulty; +import com.collectionlogmaster.domain.verification.diary.DiaryRegion; +import java.util.Map; +import java.util.Set; +import lombok.Data; +import net.runelite.api.Skill; + +@Data +public class SyncRequest { + private final Set collectionLog; + private final Map> diaries; + private final Map skills; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/SyncResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/SyncResponse.java new file mode 100644 index 00000000..63c2845c --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/response/SyncResponse.java @@ -0,0 +1,10 @@ +package com.collectionlogmaster.taskapp.response; + +import java.util.Set; +import lombok.Data; + +@Data +public class SyncResponse { + private final Set completed; + private final Set uncompleted; +} diff --git a/src/main/java/com/collectionlogmaster/util/GsonOverride.java b/src/main/java/com/collectionlogmaster/util/GsonOverride.java index 24d0a09d..d5c856c2 100644 --- a/src/main/java/com/collectionlogmaster/util/GsonOverride.java +++ b/src/main/java/com/collectionlogmaster/util/GsonOverride.java @@ -23,6 +23,7 @@ public class GsonOverride { @Inject public GsonOverride(Gson originalGson) { GsonBuilder gsonBuilder = originalGson.newBuilder() + .enableComplexMapKeySerialization() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeAdapter(Verification.class, new VerificationAdapter()) .registerTypeAdapter(VerificationMethod.class, new EnumAdapter<>(VerificationMethod.class)) From 5754756263d20204d5346338d545353d9c389e6e Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Tue, 26 May 2026 23:13:06 -0300 Subject: [PATCH 15/23] add one-time task migration --- .../CollectionLogMasterPlugin.java | 5 + .../synchronization/SyncService.java | 43 ++++- .../taskapp/TaskAppClient.java | 8 + .../taskapp/TaskAppState.java | 6 +- .../taskapp/TaskAppStateStorage.java | 1 + .../taskapp/TaskService.java | 8 +- .../taskapp/migration/MigrationHelper.java | 92 ++++++++++ .../taskapp/migration/SaveData.java | 29 ++++ .../taskapp/request/MigrateRequest.java | 8 + .../taskapp/response/UserProfileResponse.java | 3 + .../ui/InterfaceManager.java | 4 + .../ui/TooltipOverlay.java | 49 ++++++ .../ui/component/SyncButton.java | 157 ++++++++++++++++++ .../ui/component/TaskDashboard.java | 16 +- .../ui/generic/button/UIButton.java | 26 +++ 15 files changed, 436 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/collectionlogmaster/taskapp/migration/MigrationHelper.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/migration/SaveData.java create mode 100644 src/main/java/com/collectionlogmaster/taskapp/request/MigrateRequest.java create mode 100644 src/main/java/com/collectionlogmaster/ui/TooltipOverlay.java create mode 100644 src/main/java/com/collectionlogmaster/ui/component/SyncButton.java diff --git a/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java b/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java index 9d5447fe..ab88acdf 100644 --- a/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java +++ b/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java @@ -6,6 +6,7 @@ import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.InterfaceManager; import com.collectionlogmaster.ui.TaskOverlay; +import com.collectionlogmaster.ui.TooltipOverlay; import com.collectionlogmaster.util.GsonOverride; import com.google.inject.Injector; import com.google.inject.Provides; @@ -32,6 +33,9 @@ public class CollectionLogMasterPlugin extends Plugin { @Inject protected TaskOverlay taskOverlay; + @Inject + protected TooltipOverlay tooltipOverlay; + @Inject private OverlayManager overlayManager; @@ -65,6 +69,7 @@ protected void startUp() { taskmanCommand.startUp(); this.taskOverlay.setResizable(true); this.overlayManager.add(this.taskOverlay); + this.overlayManager.add(this.tooltipOverlay); } @Override diff --git a/src/main/java/com/collectionlogmaster/synchronization/SyncService.java b/src/main/java/com/collectionlogmaster/synchronization/SyncService.java index 0c0992bb..71dde0c3 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/SyncService.java +++ b/src/main/java/com/collectionlogmaster/synchronization/SyncService.java @@ -10,6 +10,8 @@ import com.collectionlogmaster.taskapp.TaskService; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import lombok.NonNull; @@ -45,7 +47,7 @@ public class SyncService { @Inject private SkillVerifier skillVerifier; - private @NonNull Verifier[] getVerifiers() { + private @NonNull Verifier[] getVerifiers() { return new Verifier[] { this.collectionLogVerifier, this.achievementDiaryVerifier, @@ -54,7 +56,7 @@ public class SyncService { } private Boolean verify(Task task) { - for (Verifier verif : this.getVerifiers()) { + for (Verifier verif : this.getVerifiers()) { if (verif.supports(task)) { return verif.verify(task); } @@ -63,8 +65,39 @@ private Boolean verify(Task task) { return null; } - public void sync() { - taskAppClient.sync( + public List check() { + return check(false); + } + + public List check(boolean excludeActive) { + Task activeTask = taskService.getActiveTask(); + List desyncedTasks = new ArrayList<>(); + + for (TaskTier tier : TaskTier.values()) { + for (Task task : taskService.getTierTasks(tier)) { + if (excludeActive && task.equals(activeTask)) { + continue; + } + + Boolean isVerified = verify(task); + if (isVerified == null) { + continue; + } + + boolean isSynced = isVerified == taskService.isComplete(task.getId()); + if (isSynced) { + continue; + } + + desyncedTasks.add(task); + } + } + + return desyncedTasks; + } + + public CompletableFuture sync() { + return taskAppClient.sync( collectionLogVerifier.verificationData(), achievementDiaryVerifier.verificationData(), skillVerifier.verificationData() @@ -89,6 +122,6 @@ public void sync() { client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, null); } }); - }).thenAccept((v) -> taskAppStateStorage.fetch()); + }).thenCompose((v) -> taskAppStateStorage.fetch()); } } diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java index 673ffb46..5d580ccf 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java @@ -3,6 +3,7 @@ import com.collectionlogmaster.CollectionLogMasterConfig; import com.collectionlogmaster.domain.verification.diary.DiaryDifficulty; import com.collectionlogmaster.domain.verification.diary.DiaryRegion; +import com.collectionlogmaster.taskapp.request.MigrateRequest; import com.collectionlogmaster.taskapp.request.SyncRequest; import com.collectionlogmaster.taskapp.request.LoginRequest; import com.collectionlogmaster.taskapp.request.UpdateTaskRequest; @@ -113,4 +114,11 @@ public CompletableFuture sync( return post(url, data, SyncResponse.class); } + + public CompletableFuture migrate(String taskId) { + HttpUrl url = buildApiUrl("user/migrate"); + MigrateRequest data = new MigrateRequest(taskId); + + return post(url, data, null); + } } diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java index 2077cad2..13c29396 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java @@ -2,7 +2,9 @@ import lombok.Data; import java.util.Set; +import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; @Data @RequiredArgsConstructor @@ -10,9 +12,11 @@ public class TaskAppState { private final String activeTaskId; private final boolean isOfficial; private final boolean isLmsEnabled; + @Accessors(fluent = true) + private final boolean hasMigrated; private final Set completedTasks; public TaskAppState() { - this(null, true, true, Set.of()); + this(null, true, true, true, Set.of()); } } diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java index 68fbefd9..9bc099cd 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java @@ -42,6 +42,7 @@ public CompletableFuture fetch() { res.getActiveTaskId(), res.isOfficial(), res.isLmsEnabled(), + res.hasMigrated(), completedTasks ); diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskService.java b/src/main/java/com/collectionlogmaster/taskapp/TaskService.java index fbae1797..c41263db 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskService.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskService.java @@ -1,10 +1,15 @@ package com.collectionlogmaster.taskapp; +import static com.collectionlogmaster.CollectionLogMasterConfig.CONFIG_GROUP; +import static com.collectionlogmaster.util.GsonOverride.GSON; + import com.collectionlogmaster.CollectionLogMasterConfig; import com.collectionlogmaster.domain.Tag; import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.TaskTier; +import com.collectionlogmaster.taskapp.response.SyncResponse; import com.collectionlogmaster.util.EventBusSubscriber; +import com.google.gson.JsonObject; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -16,6 +21,7 @@ import javax.inject.Singleton; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; import org.jetbrains.annotations.Range; @@ -49,7 +55,7 @@ public void shutDown() { @Subscribe public void onConfigChanged(ConfigChanged e) { - if (!e.getGroup().equals(CollectionLogMasterConfig.CONFIG_GROUP)) { + if (!e.getGroup().equals(CONFIG_GROUP)) { return; } diff --git a/src/main/java/com/collectionlogmaster/taskapp/migration/MigrationHelper.java b/src/main/java/com/collectionlogmaster/taskapp/migration/MigrationHelper.java new file mode 100644 index 00000000..5b765483 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/migration/MigrationHelper.java @@ -0,0 +1,92 @@ +package com.collectionlogmaster.taskapp.migration; + +import static com.collectionlogmaster.CollectionLogMasterConfig.CONFIG_GROUP; +import static com.collectionlogmaster.util.GsonOverride.GSON; + +import com.collectionlogmaster.domain.Task; +import com.collectionlogmaster.taskapp.TaskAppClient; +import com.collectionlogmaster.taskapp.TaskAppStateStorage; +import com.collectionlogmaster.taskapp.TaskService; +import java.util.concurrent.CompletableFuture; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; + +@Slf4j +@Singleton +public class MigrationHelper { + private static final String SAVE_DATA_KEY = "save-data"; + + @Inject + private ConfigManager configManager; + + @Inject + private TaskService taskService; + + @Inject + private TaskAppStateStorage taskAppStateStorage; + + @Inject + private TaskAppClient taskAppClient; + + public boolean canMigrate() { + markAsMigrated(false); + SaveData saveData = getOldSaveData(); + String oldActiveTaskId = saveData.getActiveTaskId(); + boolean isSameActiveTask = isIsSameActiveTask(oldActiveTaskId); + + return !saveData.isMigrated() + && !isSameActiveTask + && oldActiveTaskId != null + && !taskService.isComplete(oldActiveTaskId) + && !taskAppStateStorage.get().hasMigrated(); + } + + public CompletableFuture migrate() { + SaveData saveData = getOldSaveData(); + + return taskAppClient.migrate(saveData.getActiveTaskId()) + .thenRun(this::markAsMigrated); + } + + public Task getOldActiveTask() { + SaveData saveData = getOldSaveData(); + String activeTaskId = saveData.getActiveTaskId(); + + return taskService.getTaskById(activeTaskId); + } + + public SaveData getOldSaveData() { + String json = configManager.getRSProfileConfiguration(CONFIG_GROUP, SAVE_DATA_KEY); + if (json == null) { + return new SaveData(); + } + + try { + return GSON.fromJson(json, SaveData.class); + } catch (Exception ignored) { } + + return new SaveData(); + } + + public void markAsMigrated() { + markAsMigrated(true); + } + + public void markAsMigrated(boolean migrated) { + SaveData saveData = getOldSaveData(); + + saveData.setMigrated(migrated); + + String json = GSON.toJson(saveData); + configManager.setRSProfileConfiguration(CONFIG_GROUP, SAVE_DATA_KEY, json); + } + + private boolean isIsSameActiveTask(String oldActiveTaskId) { + Task newActiveTask = taskService.getActiveTask(); + + return newActiveTask != null + && newActiveTask.getId().equals(oldActiveTaskId); + } +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/migration/SaveData.java b/src/main/java/com/collectionlogmaster/taskapp/migration/SaveData.java new file mode 100644 index 00000000..d08bbf11 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/migration/SaveData.java @@ -0,0 +1,29 @@ +package com.collectionlogmaster.taskapp.migration; + +import com.google.gson.annotations.SerializedName; +import java.util.HashSet; +import java.util.Set; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@ToString +public class SaveData { + @Setter + private Integer version = 3; + + @Setter + @SerializedName("activeTaskId") + private String activeTaskId = null; + + @Setter + @SerializedName("migratedTaskId") + private String migratedTaskId = null; + + @Setter + private boolean migrated = false; + + @SerializedName("completedTasks") + private final Set completedTasks = new HashSet<>(); +} \ No newline at end of file diff --git a/src/main/java/com/collectionlogmaster/taskapp/request/MigrateRequest.java b/src/main/java/com/collectionlogmaster/taskapp/request/MigrateRequest.java new file mode 100644 index 00000000..0a71c387 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/request/MigrateRequest.java @@ -0,0 +1,8 @@ +package com.collectionlogmaster.taskapp.request; + +import lombok.Data; + +@Data +public class MigrateRequest { + private final String taskId; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java index 6d60ace1..c1e42b2b 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java +++ b/src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java @@ -3,12 +3,15 @@ import com.collectionlogmaster.taskapp.domain.CompletedTask; import java.util.List; import lombok.Data; +import lombok.experimental.Accessors; @Data public class UserProfileResponse { private final String username; private final boolean isOfficial; private final boolean isLmsEnabled; + @Accessors(fluent = true) + private final boolean hasMigrated; private final String activeTaskId; private final List completedTasks; } diff --git a/src/main/java/com/collectionlogmaster/ui/InterfaceManager.java b/src/main/java/com/collectionlogmaster/ui/InterfaceManager.java index 8c40b335..1f4f515b 100644 --- a/src/main/java/com/collectionlogmaster/ui/InterfaceManager.java +++ b/src/main/java/com/collectionlogmaster/ui/InterfaceManager.java @@ -37,6 +37,9 @@ public class InterfaceManager extends EventBusSubscriber { @Inject private StateStore stateStore; + @Inject + protected TooltipOverlay tooltipOverlay; + private MainTabbedContainer container = null; private TaskInfo taskInfo = null; @@ -152,5 +155,6 @@ public void close() { taskInfo = null; stateStore.setDashboardEnabled(false); + tooltipOverlay.clearTooltip(); } } diff --git a/src/main/java/com/collectionlogmaster/ui/TooltipOverlay.java b/src/main/java/com/collectionlogmaster/ui/TooltipOverlay.java new file mode 100644 index 00000000..59200874 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/ui/TooltipOverlay.java @@ -0,0 +1,49 @@ +package com.collectionlogmaster.ui; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.gameval.InterfaceID; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.tooltip.Tooltip; +import net.runelite.client.ui.overlay.tooltip.TooltipManager; + +@Singleton +@Slf4j +public class TooltipOverlay extends Overlay { + @Inject + private TooltipManager tooltipManager; + + private Tooltip tooltip; + + public TooltipOverlay() { + // setting these so it runs exactly after the tooltip overlay for + // the Mouse Tooltips plugin, that way we can clear its tooltip + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + drawAfterInterface(InterfaceID.TOPLEVEL_DISPLAY); + setPriority(Math.nextUp(PRIORITY_DEFAULT)); + } + + public void setTooltip(String tooltipText) { + tooltip = new Tooltip(tooltipText); + } + + public void clearTooltip() { + tooltip = null; + } + + @Override + public Dimension render(Graphics2D g) { + if (tooltip != null) { + tooltipManager.clear(); + tooltipManager.add(tooltip); + } + + return null; + } +} diff --git a/src/main/java/com/collectionlogmaster/ui/component/SyncButton.java b/src/main/java/com/collectionlogmaster/ui/component/SyncButton.java new file mode 100644 index 00000000..2264f030 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/ui/component/SyncButton.java @@ -0,0 +1,157 @@ +package com.collectionlogmaster.ui.component; + +import com.collectionlogmaster.CollectionLogMasterPlugin; +import com.collectionlogmaster.domain.Task; +import com.collectionlogmaster.synchronization.SyncService; +import com.collectionlogmaster.taskapp.migration.MigrationHelper; +import com.collectionlogmaster.ui.generic.button.UITextButton; +import java.awt.Color; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import javax.inject.Inject; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetType; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.util.ColorUtil; + +@Slf4j +public class SyncButton extends UITextButton { + public static final Color HIGHLIGHT_COLOR = new Color(0xFF981F); + + @Inject + private SyncService syncService; + + @Inject + private ClientThread clientThread; + + @Inject + private ChatboxPanelManager chatboxPanelManager; + + @Inject + private MigrationHelper migrationHelper; + + @Setter + private TaskDashboard taskDashboard; + + public static SyncButton createInside(Widget window) { + return new SyncButton(window.createChild(WidgetType.LAYER)); + } + + protected SyncButton(Widget widget) { + super(widget); + CollectionLogMasterPlugin.getStaticInjector().injectMembers(this); + } + + @Override + protected void initializeWidgets() { + super.initializeWidgets(); + + this.setText("Sync") + .setName("") + .setAction("Sync", this::sync) + .revalidate(); + } + + private void sync() { + CompletableFuture panelFuture = new CompletableFuture<>(); + + if (migrationHelper.canMigrate()) { + Task oldActiveTask = migrationHelper.getOldActiveTask(); + + String title = "
You have an old active task. Would you like to migrate it?" + + "
" + oldActiveTask.getName(); + + chatboxPanelManager.openTextMenuInput(title) + .option("1. Yes", () -> panelFuture.complete(true)) + .option("2. No", () -> panelFuture.complete(false)) + .build(); + } else { + panelFuture.complete(false); + } + + panelFuture.thenAccept( + (Boolean shouldMigrate) -> { + CompletableFuture migrationFuture = new CompletableFuture<>(); + if (shouldMigrate) { + migrationFuture = migrationHelper.migrate(); + } else { + migrationHelper.markAsMigrated(); + migrationFuture.complete(null); + } + + migrationFuture.thenRun(() -> { + // syncService.sync() has to run in the client thread for reading skills/diary data + clientThread.invoke( + () -> syncService.sync() + .thenRun(() -> clientThread.invoke(() -> taskDashboard.revalidate())) + ); + }); + } + ); + } + + private String getDesyncedTooltip(List desyncedTasks) { + if (desyncedTasks.isEmpty()) { + return ""; + } + + String header = String.format("You have %d task(s) not synced to your collection log:", desyncedTasks.size()); + String footer = desyncedTasks.size() > 5 ? String.format("
and %d more", desyncedTasks.size() - 5) : ""; + String list = desyncedTasks.stream() + .limit(5) + .map(t -> String.format("
- %s", t.getName())) + .collect(Collectors.joining()); + + return ColorUtil.wrapWithColorTag(header, HIGHLIGHT_COLOR) + + list + + ColorUtil.wrapWithColorTag(footer, Color.LIGHT_GRAY); + } + + private String getMigrationTooltip() { + Task oldActiveTask = migrationHelper.getOldActiveTask(); + if (oldActiveTask == null) { + return ""; + } + + String header = "You have a mismatching active task from your old plugin save data:"; + + return ColorUtil.wrapWithColorTag(header, HIGHLIGHT_COLOR) + + "
- " + oldActiveTask.getName(); + } + + private String getFullTooltip(List desyncedTasks) { + String migrationTooltip = getMigrationTooltip(); + String desyncedTooltip = getDesyncedTooltip(desyncedTasks); + + String separator = ""; + if (!migrationTooltip.isEmpty() && !desyncedTooltip.isEmpty()) { + separator = "

"; + } + + return migrationTooltip + separator + desyncedTooltip; + } + + @Override + public void revalidate() { + List desyncedTasks = syncService.check(true); + + if (desyncedTasks.isEmpty() && !migrationHelper.canMigrate()) { + this.setText("Sync") + .setState(State.DISABLED) + .setTooltip("You're all synced up, nothing to do there!"); + } else { + this.setText("Sync " + ColorUtil.wrapWithColorTag("(!)", HIGHLIGHT_COLOR)) + .setTooltip(getFullTooltip(desyncedTasks)); + + if (getState() == State.DISABLED) { + setState(State.DEFAULT); + } + } + + super.revalidate(); + } +} diff --git a/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java b/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java index 2f72c17f..a8ab3eee 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java +++ b/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java @@ -4,7 +4,6 @@ import com.collectionlogmaster.CollectionLogMasterPlugin; import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.synchronization.SyncService; import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.generic.UIComponent; import com.collectionlogmaster.ui.generic.UIUtil; @@ -48,15 +47,12 @@ public class TaskDashboard extends UIComponent { @Inject private TaskService taskService; - @Inject - private SyncService syncService; - private final Widget title; private final TaskComponent taskComponent; private final UITextButton completeButton; private final UITextButton generateButton; private final UITextButton faqButton; - private final UITextButton syncButton; + private final SyncButton syncButton; private final Widget progress; public static TaskDashboard createInside(Widget window) { @@ -72,7 +68,7 @@ protected TaskDashboard(Widget widget) { completeButton = UITextButton.createInside(widget); generateButton = UITextButton.createInside(widget); faqButton = UITextButton.createInside(widget); - syncButton = UITextButton.createInside(widget); + syncButton = SyncButton.createInside(widget); progress = widget.createChild(WidgetType.TEXT); initializeWidgets(); @@ -129,14 +125,10 @@ private void initializeWidgets() { syncButton.setYPositionMode(WidgetPositionMode.ABSOLUTE_BOTTOM) .setPos(BASE_GAP / 2, BASE_GAP / 2) .setSize(BUTTON_WIDTH / 2, BUTTON_HEIGHT) - .setText("Sync") - .setName(UIUtil.formatName("Sync")) - .setAction("Visit", () -> { - syncService.sync(); - revalidate(); - }) .revalidate(); + syncButton.setTaskDashboard(this); + faqButton.setXPositionMode(WidgetPositionMode.ABSOLUTE_RIGHT) .setYPositionMode(WidgetPositionMode.ABSOLUTE_BOTTOM) .setPos(BASE_GAP / 2, BASE_GAP / 2) diff --git a/src/main/java/com/collectionlogmaster/ui/generic/button/UIButton.java b/src/main/java/com/collectionlogmaster/ui/generic/button/UIButton.java index 67f1574a..cc896d17 100644 --- a/src/main/java/com/collectionlogmaster/ui/generic/button/UIButton.java +++ b/src/main/java/com/collectionlogmaster/ui/generic/button/UIButton.java @@ -1,6 +1,10 @@ package com.collectionlogmaster.ui.generic.button; +import com.collectionlogmaster.CollectionLogMasterPlugin; +import com.collectionlogmaster.ui.TooltipOverlay; import com.collectionlogmaster.ui.generic.UIComponent; +import java.awt.Color; +import javax.inject.Inject; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -8,6 +12,7 @@ import net.runelite.api.widgets.JavaScriptCallback; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetType; +import net.runelite.client.util.ColorUtil; @Accessors(chain = true) public abstract class UIButton> extends UIComponent { @@ -17,6 +22,9 @@ public enum State { DISABLED; } + @Inject + protected TooltipOverlay tooltipOverlay; + @Getter @Setter private State state = State.DEFAULT; @@ -24,12 +32,17 @@ public enum State { @Getter protected Runnable action = null; + @Getter + private String tooltip = null; + protected UIButton(Widget widget) { super(widget, WidgetType.LAYER); + CollectionLogMasterPlugin.getStaticInjector().injectMembers(this); widget.setOnOpListener((JavaScriptCallback) this::onActionSelected); widget.setOnMouseOverListener((JavaScriptCallback) this::onMouseHover); widget.setOnMouseLeaveListener((JavaScriptCallback) this::onMouseLeave); + widget.setOnMouseRepeatListener((JavaScriptCallback) this::onMouseHover); widget.setHasListener(true); } @@ -48,6 +61,10 @@ protected void onMouseHover(ScriptEvent e) { setState(State.HOVER) .revalidate(); } + + if (tooltip != null) { + tooltipOverlay.setTooltip(tooltip); + } } protected void onMouseLeave(ScriptEvent e) { @@ -55,6 +72,10 @@ protected void onMouseLeave(ScriptEvent e) { setState(State.DEFAULT) .revalidate(); } + + if (tooltip != null) { + tooltipOverlay.clearTooltip(); + } } public This setName(String name) { @@ -69,6 +90,11 @@ public This setAction(String label, Runnable action) { return castThis(); } + public This setTooltip(String tooltip) { + this.tooltip = tooltip; + return castThis(); + } + public void triggerAction() { onActionSelected(null); } From bf340d1286d8229961108b84e7f7a24c2712cf86 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Tue, 26 May 2026 23:14:06 -0300 Subject: [PATCH 16/23] prepare 2.0.0-beta2 --- build.gradle | 2 +- .../java/com/collectionlogmaster/PluginUpdateNotifier.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 6e16494c..7daddaf7 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ plugins { } group = 'com.collectionlogmaster' -version = '1.3.1' +version = '2.0.0-beta2' repositories { mavenLocal() diff --git a/src/main/java/com/collectionlogmaster/PluginUpdateNotifier.java b/src/main/java/com/collectionlogmaster/PluginUpdateNotifier.java index 28323fe6..6ef1298c 100644 --- a/src/main/java/com/collectionlogmaster/PluginUpdateNotifier.java +++ b/src/main/java/com/collectionlogmaster/PluginUpdateNotifier.java @@ -23,8 +23,7 @@ public class PluginUpdateNotifier extends EventBusSubscriber { private static final String[] UPDATE_MESSAGES = { "Collection Log Master updated to v" + getPluginVersion(), - "- Fixed issue with disabling LMS tasks while a LMS task is active", - "- Fixed issue overlay not being shown under some circumstances", + "- Thanks you for trying out the beta version! Please report any issues in Discord.", }; @Inject From 59ff30e94f05fbd681b8f0209c8bea9c0235ec58 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Fri, 29 May 2026 14:32:10 -0300 Subject: [PATCH 17/23] check if can migrate when building sync tooltip --- .../java/com/collectionlogmaster/ui/component/SyncButton.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/collectionlogmaster/ui/component/SyncButton.java b/src/main/java/com/collectionlogmaster/ui/component/SyncButton.java index 2264f030..3127b98e 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/SyncButton.java +++ b/src/main/java/com/collectionlogmaster/ui/component/SyncButton.java @@ -112,11 +112,11 @@ private String getDesyncedTooltip(List desyncedTasks) { } private String getMigrationTooltip() { - Task oldActiveTask = migrationHelper.getOldActiveTask(); - if (oldActiveTask == null) { + if (!migrationHelper.canMigrate()) { return ""; } + Task oldActiveTask = migrationHelper.getOldActiveTask(); String header = "You have a mismatching active task from your old plugin save data:"; return ColorUtil.wrapWithColorTag(header, HIGHLIGHT_COLOR) From dc8f6b94c42f5f55f5c94d9aa22939ad7a7a1d99 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Tue, 2 Jun 2026 20:28:47 -0300 Subject: [PATCH 18/23] add login required overlay --- .../taskapp/TaskAppAuthInterceptor.java | 4 + .../taskapp/TaskAppState.java | 3 +- .../taskapp/TaskAppStateStorage.java | 47 +++++---- .../ui/InterfaceManager.java | 34 +++++-- .../ui/component/LoginRequiredOverlay.java | 96 +++++++++++++++++++ .../ui/generic/UIUtil.java | 5 + 6 files changed, 162 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/collectionlogmaster/ui/component/LoginRequiredOverlay.java diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java index 70c3babd..394b3b08 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java @@ -5,6 +5,10 @@ import java.io.IOException; import java.time.Duration; import java.time.Instant; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import lombok.SneakyThrows; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java index 13c29396..5ff024a9 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java @@ -9,6 +9,7 @@ @Data @RequiredArgsConstructor public class TaskAppState { + private final boolean isLoggedIn; private final String activeTaskId; private final boolean isOfficial; private final boolean isLmsEnabled; @@ -17,6 +18,6 @@ public class TaskAppState { private final Set completedTasks; public TaskAppState() { - this(null, true, true, true, Set.of()); + this(false, null, true, true, true, Set.of()); } } diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java index 9bc099cd..1e115b4a 100644 --- a/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java @@ -2,6 +2,7 @@ import com.collectionlogmaster.command.TaskmanCommandManager; import com.collectionlogmaster.taskapp.domain.CompletedTask; +import com.collectionlogmaster.taskapp.response.UserProfileResponse; import com.collectionlogmaster.util.EventBusSubscriber; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -9,6 +10,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; @Singleton @Slf4j @@ -33,25 +35,32 @@ public TaskAppState get() { public CompletableFuture fetch() { return taskAppClient.getUserProfile() - .thenAccept(res -> { - Set completedTasks = res.getCompletedTasks().stream() - .map(CompletedTask::getId) - .collect(Collectors.toUnmodifiableSet()); - - TaskAppState newState = new TaskAppState( - res.getActiveTaskId(), - res.isOfficial(), - res.isLmsEnabled(), - res.hasMigrated(), - completedTasks - ); - - if (state.equals(newState)) { - return; - } - - state = newState; - taskmanCommandManager.updateServer(); + .thenAccept(this::updateState) + .exceptionally(t -> { + state = new TaskAppState(); + throw new RuntimeException(t.getMessage(), t); }); } + + private void updateState(UserProfileResponse res) { + Set completedTasks = res.getCompletedTasks().stream() + .map(CompletedTask::getId) + .collect(Collectors.toUnmodifiableSet()); + + TaskAppState newState = new TaskAppState( + true, + res.getActiveTaskId(), + res.isOfficial(), + res.isLmsEnabled(), + res.hasMigrated(), + completedTasks + ); + + if (state.equals(newState)) { + return; + } + + state = newState; + taskmanCommandManager.updateServer(); + } } diff --git a/src/main/java/com/collectionlogmaster/ui/InterfaceManager.java b/src/main/java/com/collectionlogmaster/ui/InterfaceManager.java index 1f4f515b..9d387675 100644 --- a/src/main/java/com/collectionlogmaster/ui/InterfaceManager.java +++ b/src/main/java/com/collectionlogmaster/ui/InterfaceManager.java @@ -1,9 +1,12 @@ package com.collectionlogmaster.ui; import com.collectionlogmaster.domain.Task; +import com.collectionlogmaster.taskapp.TaskAppStateStorage; +import com.collectionlogmaster.ui.component.LoginRequiredOverlay; import com.collectionlogmaster.ui.component.MainTabbedContainer; import com.collectionlogmaster.ui.component.MenuManager; import com.collectionlogmaster.ui.component.TaskInfo; +import com.collectionlogmaster.ui.generic.UIComponent; import com.collectionlogmaster.ui.sprites.SpriteManager; import com.collectionlogmaster.ui.state.StateStore; import com.collectionlogmaster.util.EventBusSubscriber; @@ -16,6 +19,7 @@ import net.runelite.api.events.WidgetLoaded; import net.runelite.api.gameval.InterfaceID; import net.runelite.api.widgets.Widget; +import net.runelite.client.callback.ClientThread; import net.runelite.client.eventbus.Subscribe; import org.jetbrains.annotations.Nullable; @@ -28,6 +32,9 @@ public class InterfaceManager extends EventBusSubscriber { @Inject private Client client; + @Inject + private ClientThread clientThread; + @Inject private SpriteManager spriteManager; @@ -37,10 +44,13 @@ public class InterfaceManager extends EventBusSubscriber { @Inject private StateStore stateStore; + @Inject + private TaskAppStateStorage taskAppStateStorage; + @Inject protected TooltipOverlay tooltipOverlay; - private MainTabbedContainer container = null; + private UIComponent container = null; private TaskInfo taskInfo = null; @@ -106,14 +116,24 @@ public void openMainContainer() { return; } - hideCollectionLogContent(true); + taskAppStateStorage.fetch() + .whenComplete((v, t) -> { + clientThread.invoke(() -> { + hideCollectionLogContent(true); - if (container != null) { - container.unregister(); - } + if (container != null) { + container.unregister(); + } - container = MainTabbedContainer.createInside(content); - container.revalidate(); + if (taskAppStateStorage.get().isLoggedIn()) { + container = MainTabbedContainer.createInside(content); + } else { + container = LoginRequiredOverlay.createInside(content); + } + + container.revalidate(); + }); + }); } public void openTaskInfo(Task task) { diff --git a/src/main/java/com/collectionlogmaster/ui/component/LoginRequiredOverlay.java b/src/main/java/com/collectionlogmaster/ui/component/LoginRequiredOverlay.java new file mode 100644 index 00000000..eb191880 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/ui/component/LoginRequiredOverlay.java @@ -0,0 +1,96 @@ +package com.collectionlogmaster.ui.component; + +import com.collectionlogmaster.CollectionLogMasterPlugin; +import com.collectionlogmaster.taskapp.TaskService; +import com.collectionlogmaster.ui.generic.UIComponent; +import com.collectionlogmaster.ui.generic.UIUtil; +import com.collectionlogmaster.ui.generic.button.UIButton.State; +import com.collectionlogmaster.ui.generic.button.UITextButton; +import javax.inject.Inject; +import net.runelite.api.FontID; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetPositionMode; +import net.runelite.api.widgets.WidgetSizeMode; +import net.runelite.api.widgets.WidgetTextAlignment; +import net.runelite.api.widgets.WidgetType; +import net.runelite.client.callback.ClientThread; + +public class LoginRequiredOverlay extends UIComponent { + public static final int BASE_GAP = 16; + public static final int TITLE_HEIGHT = 50; + public static final int BUTTON_HEIGHT = 30; + public static final int BUTTON_WIDTH = 140; + public static final int BODY_HEIGHT = 70; + + private final Widget title; + private final Widget body; + private final UITextButton taskAppButton; + + public static LoginRequiredOverlay createInside(Widget window) { + return new LoginRequiredOverlay(window.createChild(WidgetType.LAYER)); + } + + protected LoginRequiredOverlay(Widget widget) { + super(widget, WidgetType.LAYER); + CollectionLogMasterPlugin.getStaticInjector().injectMembers(this); + + title = widget.createChild(WidgetType.TEXT); + body = widget.createChild(WidgetType.TEXT); + taskAppButton = UITextButton.createInside(widget); + + initializeWidgets(); + } + + private void initializeWidgets() { + widget.setPos(0, 0) + .setWidthMode(WidgetSizeMode.MINUS) + .setHeightMode(WidgetSizeMode.MINUS) + .setSize(0, 0) + .revalidate(); + + title.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER) + .setPos(0, BASE_GAP) + .setWidthMode(WidgetSizeMode.MINUS) + .setSize(0, TITLE_HEIGHT) + .setFontId(FontID.QUILL_CAPS_LARGE) + .setTextColor(0xFFA0A0) + .setTextShadowed(true) + .setXTextAlignment(WidgetTextAlignment.CENTER) + .setYTextAlignment(WidgetTextAlignment.CENTER) + .setText("Login Required") + .revalidate(); + + body.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER) + .setPos(0, title.getRelativeY() + title.getHeight() + BASE_GAP) + .setWidthMode(WidgetSizeMode.MINUS) + .setSize(BASE_GAP * 2, BODY_HEIGHT) + .setFontId(FontID.BOLD_12) + .setTextColor(0xFFFFFF) + .setTextShadowed(true) + .setXTextAlignment(WidgetTextAlignment.CENTER) + .setYTextAlignment(WidgetTextAlignment.CENTER) + .setText( + "You need to input valid TaskApp credentials in the plugin configuration in order to use the it!" + + " If you don't have an account, click the button below to visit the website and register." + + "

Close the dashboard and open it again after doing that." + ) + .revalidate(); + + taskAppButton.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER) + .setPos(0, body.getRelativeY() + body.getHeight() + BASE_GAP) + .setSize(BUTTON_WIDTH, BUTTON_HEIGHT) + .setText("Visit TaskApp") + .setName(UIUtil.formatName("TaskApp")) + .setAction("Visit", UIUtil::openTaskApp) + .revalidate(); + } + + @Override + public void revalidate() { + super.revalidate(); + + title.revalidate(); + body.revalidate(); + taskAppButton.revalidate(); + } +} diff --git a/src/main/java/com/collectionlogmaster/ui/generic/UIUtil.java b/src/main/java/com/collectionlogmaster/ui/generic/UIUtil.java index 72ad1778..7e389642 100644 --- a/src/main/java/com/collectionlogmaster/ui/generic/UIUtil.java +++ b/src/main/java/com/collectionlogmaster/ui/generic/UIUtil.java @@ -11,6 +11,7 @@ public class UIUtil { private static final String WIDGET_NAME_FORMAT = "%s"; private static final String BASE_OSRS_WIKI_URL = "https://oldschool.runescape.wiki/w/%s"; + public static final String TASKAPP_URL = "https://www.osrstaskapp.com/"; public static final String FAQ_URL = "https://docs.google.com/document/d/e/2PACX-1vTHfXHzMQFbt_iYAP-O88uRhhz3wigh1KMiiuomU7ftli-rL_c3bRqfGYmUliE1EHcIr3LfMx2UTf2U/pub"; @@ -23,6 +24,10 @@ public static void openFAQ() { LinkBrowser.browse(FAQ_URL); } + public static void openTaskApp() { + LinkBrowser.browse(TASKAPP_URL); + } + public static String formatName(String name) { return String.format(WIDGET_NAME_FORMAT, name); } From 3a3735aa6a9a1ddac448a37c549055a3a1cbc116 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Tue, 2 Jun 2026 20:33:34 -0300 Subject: [PATCH 19/23] don't actually overwrite other tooltips --- .../com/collectionlogmaster/ui/TooltipOverlay.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/com/collectionlogmaster/ui/TooltipOverlay.java b/src/main/java/com/collectionlogmaster/ui/TooltipOverlay.java index 59200874..3c928d40 100644 --- a/src/main/java/com/collectionlogmaster/ui/TooltipOverlay.java +++ b/src/main/java/com/collectionlogmaster/ui/TooltipOverlay.java @@ -20,15 +20,6 @@ public class TooltipOverlay extends Overlay { private Tooltip tooltip; - public TooltipOverlay() { - // setting these so it runs exactly after the tooltip overlay for - // the Mouse Tooltips plugin, that way we can clear its tooltip - setPosition(OverlayPosition.DYNAMIC); - setLayer(OverlayLayer.ABOVE_WIDGETS); - drawAfterInterface(InterfaceID.TOPLEVEL_DISPLAY); - setPriority(Math.nextUp(PRIORITY_DEFAULT)); - } - public void setTooltip(String tooltipText) { tooltip = new Tooltip(tooltipText); } @@ -40,7 +31,6 @@ public void clearTooltip() { @Override public Dimension render(Graphics2D g) { if (tooltip != null) { - tooltipManager.clear(); tooltipManager.add(tooltip); } From ec54ef6eabf4e08b8f00fc7d2acb2494972ec33e Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Tue, 2 Jun 2026 20:37:19 -0300 Subject: [PATCH 20/23] update build script and set build type in plugin properties --- build.gradle | 22 +++++++++++++++------- runelite-plugin.properties | 3 ++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 7daddaf7..b5d0fa2a 100644 --- a/build.gradle +++ b/build.gradle @@ -30,17 +30,17 @@ repositories { } def runeLiteVersion = 'latest.release' +def pluginMainClass = 'com.collectionlogmaster.CollectionLogMasterPluginTest' dependencies { compileOnly group: 'net.runelite', name:'client', version: runeLiteVersion - compileOnly 'org.jetbrains:annotations:23.0.0' - compileOnly 'org.projectlombok:lombok:1.18.30' - annotationProcessor 'org.projectlombok:lombok:1.18.30' + compileOnly 'org.projectlombok:lombok:1.18.30' + annotationProcessor 'org.projectlombok:lombok:1.18.30' - testImplementation 'junit:junit:4.12' - testImplementation group: 'net.runelite', name:'client', version: runeLiteVersion - testImplementation group: 'net.runelite', name:'jshell', version: runeLiteVersion + testImplementation 'junit:junit:4.12' + testImplementation group: 'net.runelite', name:'client', version: runeLiteVersion + testImplementation group: 'net.runelite', name:'jshell', version: runeLiteVersion } tasks.withType(JavaCompile).configureEach { @@ -48,10 +48,18 @@ tasks.withType(JavaCompile).configureEach { options.release.set(11) } +tasks.register('run', JavaExec) { + classpath = sourceSets.test.runtimeClasspath + mainClass = pluginMainClass + + jvmArgs "-ea" + args "--developer-mode", "--debug" +} + tasks.register('shadowJar', Jar) { dependsOn configurations.testRuntimeClasspath manifest { - attributes('Main-Class': 'com.collectionlogmaster.CollectionLogMasterPluginTest', 'Multi-Release': true) + attributes('Main-Class': pluginMainClass, 'Multi-Release': true) } duplicatesStrategy = DuplicatesStrategy.EXCLUDE diff --git a/runelite-plugin.properties b/runelite-plugin.properties index 1b1c29f2..78e2d44f 100644 --- a/runelite-plugin.properties +++ b/runelite-plugin.properties @@ -2,4 +2,5 @@ displayName=Collection Log Master author=ImTedious description=In-game dashboard to roll your next grind; official plugin for the Taskman game mode tags=task,tasker,taskman,taskapp,generate,collection,log,generatetask,tedious -plugins=com.collectionlogmaster.CollectionLogMasterPlugin \ No newline at end of file +plugins=com.collectionlogmaster.CollectionLogMasterPlugin +build=custom \ No newline at end of file From 1d78bd9f47fe618b5887dfe1298cc50eb5392dec Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Tue, 2 Jun 2026 20:38:15 -0300 Subject: [PATCH 21/23] bump version for beta3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b5d0fa2a..0c0b40f5 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ plugins { } group = 'com.collectionlogmaster' -version = '2.0.0-beta2' +version = '2.0.0-beta3' repositories { mavenLocal() From ffc9c509236b70d42b0c2448b57e8c2afd763ab5 Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Tue, 2 Jun 2026 20:51:43 -0300 Subject: [PATCH 22/23] bring back jetbrains annotations --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 0c0b40f5..f485f975 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,7 @@ def pluginMainClass = 'com.collectionlogmaster.CollectionLogMasterPluginTest' dependencies { compileOnly group: 'net.runelite', name:'client', version: runeLiteVersion + compileOnly 'org.jetbrains:annotations:23.0.0' compileOnly 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.30' From af0ae0de37c36a53f6ec29160796f6730e8b60ca Mon Sep 17 00:00:00 2001 From: Raphael Mobis Tacla Date: Tue, 2 Jun 2026 21:21:43 -0300 Subject: [PATCH 23/23] set build to gradle not custom --- runelite-plugin.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-plugin.properties b/runelite-plugin.properties index 78e2d44f..9d69bdc3 100644 --- a/runelite-plugin.properties +++ b/runelite-plugin.properties @@ -3,4 +3,4 @@ author=ImTedious description=In-game dashboard to roll your next grind; official plugin for the Taskman game mode tags=task,tasker,taskman,taskapp,generate,collection,log,generatetask,tedious plugins=com.collectionlogmaster.CollectionLogMasterPlugin -build=custom \ No newline at end of file +build=gradle \ No newline at end of file