fix: ntfy use JSON publish API (raw body was treated as attachment)

ntfy.sh now treats any request body (PUT/POST, text/plain) as a file
attachment, so messages never showed in the app. Switch to ntfy's JSON
publish API: POST / with {topic,title,message}.

version 1.7.1 -> 1.7.2 (code 14 -> 15)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-09 20:32:26 +08:00
parent 0238b5dd8d
commit 94422fbd76
2 changed files with 15 additions and 11 deletions
+2 -2
View File
@@ -13,8 +13,8 @@ android {
applicationId = "com.a2i.forwarder" applicationId = "com.a2i.forwarder"
minSdk = 34 minSdk = 34
targetSdk = 36 targetSdk = 36
versionCode = 14 versionCode = 15
versionName = "1.7.1" versionName = "1.7.2"
} }
signingConfigs { signingConfigs {
@@ -10,11 +10,13 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.io.IOException import java.io.IOException
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
* ntfy 推送:POST {server}/{topic},可选 Bearer auth * ntfy 推送:用 JSON publish APIPOST / with {topic,title,message}
* 注:raw body 会被 ntfy 当附件,必须用 JSON API 或 Message header。
* 对瞬时 IOException 自动重试 3 次(退避 600/1200ms)。 * 对瞬时 IOException 自动重试 3 次(退避 600/1200ms)。
*/ */
object NtfySender { object NtfySender {
@@ -30,13 +32,15 @@ object NtfySender {
title: String, body: String, title: String, body: String,
): Result<Unit> = withContext(Dispatchers.IO) { ): Result<Unit> = withContext(Dispatchers.IO) {
runCatching { runCatching {
val url = server.trim().removeSuffix("/") + "/" + topic.trim() val payload = JSONObject()
val req = Request.Builder().url(url) .put("topic", topic.trim())
.header("Title", title.take(200)) .put("title", title.take(200))
.apply { .put("message", body.take(4000))
if (token.isNotBlank()) header("Authorization", "Bearer $token") .toString()
} val req = Request.Builder()
.put(body.take(4000).toRequestBody(TEXT_PLAIN)) .url(server.trim().removeSuffix("/"))
.apply { if (token.isNotBlank()) header("Authorization", "Bearer $token") }
.post(payload.toRequestBody(JSON))
.build() .build()
doPushWithRetry(req) doPushWithRetry(req)
} }
@@ -68,7 +72,7 @@ object NtfySender {
} }
} }
private val TEXT_PLAIN = "text/plain; charset=utf-8".toMediaType() private val JSON = "application/json; charset=utf-8".toMediaType()
private const val MAX_ATTEMPTS = 3 private const val MAX_ATTEMPTS = 3
private val BACKOFF_MS = longArrayOf(600L, 1200L) private val BACKOFF_MS = longArrayOf(600L, 1200L)
} }