From 13df0dfcecde9d0aeaa6b73d331234cd0baa95dc Mon Sep 17 00:00:00 2001 From: atsunatsu Date: Mon, 7 Sep 2026 02:11:40 +0800 Subject: [PATCH] fix: Wavelog sync via v1 API logbook_get_worked_grids (3.0.2 has no API v2) --- .../core/data/repository/WavelogRepository.kt | 66 ++++++++++--------- .../src/main/res/values-zh/strings.xml | 4 +- .../src/main/res/values/strings.xml | 4 +- .../feature/settings/SettingsDialog.kt | 2 +- 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/WavelogRepository.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/WavelogRepository.kt index 01647d04..9da95384 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/WavelogRepository.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/WavelogRepository.kt @@ -20,64 +20,68 @@ package com.rtbishop.look4sat.core.data.repository import com.rtbishop.look4sat.core.domain.repository.IWavelogRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.json.JSONArray import org.json.JSONObject /** * Fetches the list of worked gridsquares from a self-hosted Wavelog instance. * - * Uses the Wavelog API v2 endpoint (Wavelog >= 3.1.0): - * GET {base}/index.php/api/v2/lookup?grid=all - * Authorization: Bearer wl2_... - * Response: {"data": {"grids": ["JN47", "JO30"], "count": 2}, ...} + * Uses the Wavelog v1 API endpoint (verified against Wavelog 3.0.2): + * POST {base}/index.php/api/logbook_get_worked_grids + * Body: {"key": "", "logbook_id": ""} + * Response 201: JSON array of 4-char gridsquares, e.g. ["JN47","JO30"] + * (see Wavelog Api.php logbook_get_worked_grids / Api_model::get_grids_worked_in_logbook) * - * Uses a plain HttpURLConnection (bearer auth header) — IRemoteSource does not - * support custom headers, and no feature module touches this class directly. + * The token field carries ":" so one credential line + * configures both values (the dialog explains this to the user). */ class WavelogRepository : IWavelogRepository { - /** - * Fetch worked grids. Returns the grid set, or null on any failure - * (network error, non-200, malformed response, bad token). - */ override suspend fun fetchWorkedGrids(url: String, token: String): Set? = withContext(Dispatchers.IO) { - if (url.isBlank() || token.isBlank()) return@withContext null + val parts = token.trim().split(":") + val apiKey = parts.getOrNull(0)?.trim().orEmpty() + val logbookId = parts.getOrNull(1)?.trim().orEmpty() + if (url.isBlank() || apiKey.isBlank() || logbookId.isBlank()) return@withContext null val base = url.trim().trimEnd('/') - val requestUrl = "$base/index.php/api/v2/lookup?grid=all" + val requestUrl = "$base/index.php/api/logbook_get_worked_grids" try { - // IRemoteSource.getNetworkStream does not support custom headers, so - // run the bearer-token request directly here via java.net (simple GET, - // no OkHttp dependency added to this module's public API). val connection = java.net.URL(requestUrl).openConnection() as java.net.HttpURLConnection - connectionTimeouts(connection) - connection.setRequestProperty("Authorization", "Bearer ${token.trim()}") - connection.requestMethod = "GET" + connection.connectTimeout = 10_000 + connection.readTimeout = 15_000 + connection.requestMethod = "POST" + connection.doOutput = true + connection.setRequestProperty("Content-Type", "application/json") + val body = JSONObject().apply { + put("key", apiKey) + put("logbook_id", logbookId) + }.toString().toByteArray() + connection.outputStream.use { it.write(body) } val code = connection.responseCode - if (code != 200) { + if (code !in 200..299) { connection.disconnect() return@withContext null } - val body = connection.inputStream.bufferedReader().use { it.readText() } + val response = connection.inputStream.bufferedReader().use { it.readText() } connection.disconnect() - parseWorkedGrids(body) + parseWorkedGrids(response) } catch (e: Exception) { println("WavelogRepository fetch failure: $e") null } } - private fun connectionTimeouts(connection: java.net.HttpURLConnection) { - connection.connectTimeout = 10_000 - connection.readTimeout = 15_000 - } - internal fun parseWorkedGrids(body: String): Set? { return try { - val obj = JSONObject(body) - val data = obj.optJSONObject("data") ?: return null - val grids = data.optJSONArray("grids") ?: return null + val array = when { + body.trimStart().startsWith("[") -> JSONArray(body) + else -> { + // Tolerate {"grids": [...]} wrappers too + JSONObject(body).optJSONArray("grids") ?: return null + } + } val result = mutableSetOf() - for (i in 0 until grids.length()) { - val grid = grids.optString(i, "").trim().uppercase() + for (i in 0 until array.length()) { + val grid = array.optString(i, "").trim().uppercase() // Keep only well-formed 4-character Maidenhead squares (e.g. OL62) if (grid.length == 4 && grid[0] in 'A'..'R' && grid[1] in 'A'..'R' && grid[2] in '0'..'9' && grid[3] in '0'..'9' diff --git a/core/presentation/src/main/res/values-zh/strings.xml b/core/presentation/src/main/res/values-zh/strings.xml index 448f6ff6..07ba4397 100644 --- a/core/presentation/src/main/res/values-zh/strings.xml +++ b/core/presentation/src/main/res/values-zh/strings.xml @@ -144,8 +144,8 @@ 已从 Wavelog 同步 — 通联 %1$d 个网格 未配置 — 连接你的 Wavelog 日志以在地图上高亮已通联网格 Wavelog 地址 - API 令牌 - 已同步网格数:%1$d。请先保存,再点同步。 + API key :日志 ID + 已同步网格数:%1$d。格式:API key:日志ID。请先保存,再点同步。 立即同步 同步中… 使用梅登黑德网格设置站点位置 diff --git a/core/presentation/src/main/res/values/strings.xml b/core/presentation/src/main/res/values/strings.xml index edb5fbc7..fece922e 100644 --- a/core/presentation/src/main/res/values/strings.xml +++ b/core/presentation/src/main/res/values/strings.xml @@ -179,8 +179,8 @@ Synced from Wavelog — %1$d grids worked Not configured — connect your Wavelog logbook to highlight worked grids on the map Wavelog URL - API token - Worked grids synced: %1$d. Save first, then tap Sync. + API key : logbook ID + Worked grids synced: %1$d. Format: API key, colon, logbook ID. Save first, then tap Sync. Sync now Syncing… Set station\'s position using locator diff --git a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsDialog.kt b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsDialog.kt index 53b724a3..4ae91dd3 100644 --- a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsDialog.kt +++ b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsDialog.kt @@ -188,7 +188,7 @@ fun WavelogDialog( value = token.value, onValueChange = { token.value = it }, label = { Text(text = stringResource(id = R.string.prefs_wavelog_token)) }, - placeholder = { Text(text = "wl2_...") }, + placeholder = { Text(text = "abcdef123456:1") }, singleLine = true, modifier = Modifier.fillMaxWidth().padding(horizontal = LocalSpacing.current.large), )