diff --git a/app/build.gradle b/app/build.gradle index 0aa3459..eea699c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.3' // Firebase, sigh ... (only Google Play) - playImplementation 'com.google.firebase:firebase-messaging:23.0.1' + playImplementation 'com.google.firebase:firebase-messaging:23.0.0' // RecyclerView implementation "androidx.recyclerview:recyclerview:1.3.0-alpha01" diff --git a/app/schemas/io.heckel.ntfy.db.Database/8.json b/app/schemas/io.heckel.ntfy.db.Database/8.json index 2f25cff..5ef4e3e 100644 --- a/app/schemas/io.heckel.ntfy.db.Database/8.json +++ b/app/schemas/io.heckel.ntfy.db.Database/8.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 8, - "identityHash": "eda2cb9740c4542f24462779eb6ff81d", + "identityHash": "5bab75c3b41c53c9855fe3a7ef8f0669", "entities": [ { "tableName": "Subscription", @@ -82,7 +82,7 @@ }, { "tableName": "Notification", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))", "fields": [ { "fieldPath": "id", @@ -114,6 +114,12 @@ "affinity": "TEXT", "notNull": true }, + { + "fieldPath": "encoding", + "columnName": "encoding", + "affinity": "TEXT", + "notNull": true + }, { "fieldPath": "notificationId", "columnName": "notificationId", @@ -284,7 +290,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'eda2cb9740c4542f24462779eb6ff81d')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5bab75c3b41c53c9855fe3a7ef8f0669')" ] } } \ No newline at end of file diff --git a/app/schemas/io.heckel.ntfy.db.Database/9.json b/app/schemas/io.heckel.ntfy.db.Database/9.json new file mode 100644 index 0000000..024364b --- /dev/null +++ b/app/schemas/io.heckel.ntfy.db.Database/9.json @@ -0,0 +1,296 @@ +{ + "formatVersion": 1, + "database": { + "version": 9, + "identityHash": "5bab75c3b41c53c9855fe3a7ef8f0669", + "entities": [ + { + "tableName": "Subscription", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, `upAppId` TEXT, `upConnectorToken` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "baseUrl", + "columnName": "baseUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "instant", + "columnName": "instant", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mutedUntil", + "columnName": "mutedUntil", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "upAppId", + "columnName": "upAppId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "upConnectorToken", + "columnName": "upConnectorToken", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_Subscription_baseUrl_topic", + "unique": true, + "columnNames": [ + "baseUrl", + "topic" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_baseUrl_topic` ON `${TABLE_NAME}` (`baseUrl`, `topic`)" + }, + { + "name": "index_Subscription_upConnectorToken", + "unique": true, + "columnNames": [ + "upConnectorToken" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_upConnectorToken` ON `${TABLE_NAME}` (`upConnectorToken`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subscriptionId", + "columnName": "subscriptionId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "encoding", + "columnName": "encoding", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "3" + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "click", + "columnName": "click", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attachment.name", + "columnName": "attachment_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachment.type", + "columnName": "attachment_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachment.size", + "columnName": "attachment_size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachment.expires", + "columnName": "attachment_expires", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachment.url", + "columnName": "attachment_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachment.contentUri", + "columnName": "attachment_contentUri", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachment.progress", + "columnName": "attachment_progress", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id", + "subscriptionId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "User", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`baseUrl` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, PRIMARY KEY(`baseUrl`))", + "fields": [ + { + "fieldPath": "baseUrl", + "columnName": "baseUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "baseUrl" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Log", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `tag` TEXT NOT NULL, `level` INTEGER NOT NULL, `message` TEXT NOT NULL, `exception` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "exception", + "columnName": "exception", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5bab75c3b41c53c9855fe3a7ef8f0669')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 066e32e..8300aad 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -108,6 +108,7 @@ + diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index 8da20f5..7b05ff3 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -50,6 +50,7 @@ data class Notification( @ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp @ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "message") val message: String, + @ColumnInfo(name = "encoding") val encoding: String, // "base64" or "" @ColumnInfo(name = "notificationId") val notificationId: Int, // Android notification popup ID @ColumnInfo(name = "priority", defaultValue = "3") val priority: Int, // 1=min, 3=default, 5=max @ColumnInfo(name = "tags") val tags: String, @@ -100,7 +101,7 @@ data class LogEntry( this(0, timestamp, tag, level, message, exception) } -@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 8) +@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 9) abstract class Database : RoomDatabase() { abstract fun subscriptionDao(): SubscriptionDao abstract fun notificationDao(): NotificationDao @@ -122,6 +123,7 @@ abstract class Database : RoomDatabase() { .addMigrations(MIGRATION_5_6) .addMigrations(MIGRATION_6_7) .addMigrations(MIGRATION_7_8) + .addMigrations(MIGRATION_8_9) .fallbackToDestructiveMigration() .build() this.instance = instance @@ -191,6 +193,12 @@ abstract class Database : RoomDatabase() { db.execSQL("CREATE TABLE User (baseUrl TEXT NOT NULL, username TEXT NOT NULL, password TEXT NOT NULL, PRIMARY KEY(baseUrl))") } } + + private val MIGRATION_8_9 = object : Migration(8, 9) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Notification ADD COLUMN encoding TEXT NOT NULL DEFAULT('')") + } + } } } diff --git a/app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt b/app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt index 1204323..d39bb6c 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt @@ -2,13 +2,12 @@ package io.heckel.ntfy.msg import android.content.Context import android.content.Intent +import android.util.Base64 import io.heckel.ntfy.R import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription -import io.heckel.ntfy.util.Log -import io.heckel.ntfy.util.joinTagsMap -import io.heckel.ntfy.util.splitTags +import io.heckel.ntfy.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -26,7 +25,9 @@ class BroadcastService(private val ctx: Context) { intent.putExtra("topic", subscription.topic) intent.putExtra("time", notification.timestamp.toInt()) intent.putExtra("title", notification.title) - intent.putExtra("message", notification.message) + intent.putExtra("message", decodeMessage(notification)) + intent.putExtra("message_bytes", decodeBytesMessage(notification)) + intent.putExtra("message_encoding", notification.encoding) intent.putExtra("tags", notification.tags) intent.putExtra("tags_map", joinTagsMap(splitTags(notification.tags))) intent.putExtra("priority", notification.priority) diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt index 8bd5f2b..bfc4f86 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt @@ -1,11 +1,13 @@ package io.heckel.ntfy.msg import android.content.Context +import android.util.Base64 import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.util.Log import io.heckel.ntfy.up.Distributor +import io.heckel.ntfy.util.decodeBytesMessage import io.heckel.ntfy.util.safeLet /** @@ -37,7 +39,7 @@ class NotificationDispatcher(val context: Context, val repository: Repository) { } if (distribute) { safeLet(subscription.upAppId, subscription.upConnectorToken) { appId, connectorToken -> - distributor.sendMessage(appId, connectorToken, notification.message) + distributor.sendMessage(appId, connectorToken, decodeBytesMessage(notification)) } } if (download) { diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt index 150b8ca..48b873b 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt @@ -20,11 +20,6 @@ class NotificationParser { if (message.event != ApiService.EVENT_MESSAGE) { return null } - val decodedMessage = if (message.encoding == MESSAGE_ENCODING_BASE64) { - String(Base64.decode(message.message, Base64.DEFAULT)) - } else { - message.message - } val attachment = if (message.attachment?.url != null) { Attachment( name = message.attachment.name, @@ -39,7 +34,8 @@ class NotificationParser { subscriptionId = subscriptionId, timestamp = message.time, title = message.title ?: "", - message = decodedMessage, + message = message.message, + encoding = message.encoding ?: "", priority = toPriority(message.priority), tags = joinTags(message.tags), click = message.click ?: "", diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt index 97fdc82..f9f758b 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -39,7 +39,7 @@ class NotificationService(val context: Context) { fun cancel(notification: Notification) { if (notification.notificationId != 0) { - Log.d(TAG, "Cancelling notification ${notification.id}: ${notification.message}") + Log.d(TAG, "Cancelling notification ${notification.id}: ${decodeMessage(notification)}") notificationManager.cancel(notification.notificationId) } } diff --git a/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt b/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt index 7002290..9ccb93e 100644 --- a/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt +++ b/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt @@ -87,8 +87,8 @@ class JsonConnection( override fun close() { Log.d(TAG, "[$url] Cancelling connection") - if (this::job.isInitialized) job?.cancel() - if (this::call.isInitialized) call?.cancel() + if (this::job.isInitialized) job.cancel() + if (this::call.isInitialized) call.cancel() } private fun nextRetryMillis(retryMillis: Long, startTime: Long): Long { diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt index 29288e6..6008765 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -7,6 +7,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.text.Html +import android.util.Base64 import android.view.ActionMode import android.view.Menu import android.view.MenuItem @@ -29,6 +30,7 @@ import io.heckel.ntfy.db.Repository import io.heckel.ntfy.firebase.FirebaseMessenger import io.heckel.ntfy.util.Log import io.heckel.ntfy.msg.ApiService +import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64 import io.heckel.ntfy.msg.NotificationService import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.util.* @@ -514,9 +516,10 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra private fun copyToClipboard(notification: Notification) { runOnUiThread { - val message = notification.message + "\n\n" + Date(notification.timestamp * 1000).toString() + val message = decodeMessage(notification) + val text = message + "\n\n" + Date(notification.timestamp * 1000).toString() val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText("notification message", message) + val clip = ClipData.newPlainText("notification message", text) clipboard.setPrimaryClip(clip) Toast .makeText(this, getString(R.string.detail_copied_to_clipboard_message), Toast.LENGTH_LONG) @@ -574,7 +577,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra val content = adapter.selected.joinToString("\n\n") { notificationId -> val notification = repository.getNotification(notificationId) notification?.let { - it.message + "\n" + Date(it.timestamp * 1000).toString() + decodeMessage(it) + "\n" + Date(it.timestamp * 1000).toString() }.orEmpty() } runOnUiThread { diff --git a/app/src/main/java/io/heckel/ntfy/up/Constants.kt b/app/src/main/java/io/heckel/ntfy/up/Constants.kt index 6fd5d97..8c9ddaf 100644 --- a/app/src/main/java/io/heckel/ntfy/up/Constants.kt +++ b/app/src/main/java/io/heckel/ntfy/up/Constants.kt @@ -13,7 +13,10 @@ const val ACTION_MESSAGE = "org.unifiedpush.android.connector.MESSAGE" const val ACTION_REGISTER = "org.unifiedpush.android.distributor.REGISTER" const val ACTION_UNREGISTER = "org.unifiedpush.android.distributor.UNREGISTER" +const val FEATURE_BYTES_MESSAGE = "org.unifiedpush.android.distributor.feature.BYTES_MESSAGE" + const val EXTRA_APPLICATION = "application" const val EXTRA_TOKEN = "token" const val EXTRA_ENDPOINT = "endpoint" const val EXTRA_MESSAGE = "message" +const val EXTRA_BYTES_MESSAGE = "bytesMessage" diff --git a/app/src/main/java/io/heckel/ntfy/up/Distributor.kt b/app/src/main/java/io/heckel/ntfy/up/Distributor.kt index 29b1727..1e9b7c1 100644 --- a/app/src/main/java/io/heckel/ntfy/up/Distributor.kt +++ b/app/src/main/java/io/heckel/ntfy/up/Distributor.kt @@ -9,13 +9,14 @@ import io.heckel.ntfy.util.Log * See https://unifiedpush.org/spec/android/ for details. */ class Distributor(val context: Context) { - fun sendMessage(app: String, connectorToken: String, message: String) { - Log.d(TAG, "Sending MESSAGE to $app (token=$connectorToken): $message") + fun sendMessage(app: String, connectorToken: String, message: ByteArray) { + Log.d(TAG, "Sending MESSAGE to $app (token=$connectorToken): ${String(message)} (${message.size} bytes)}") val broadcastIntent = Intent() broadcastIntent.`package` = app broadcastIntent.action = ACTION_MESSAGE broadcastIntent.putExtra(EXTRA_TOKEN, connectorToken) - broadcastIntent.putExtra(EXTRA_MESSAGE, message) + broadcastIntent.putExtra(EXTRA_MESSAGE, String(message)) // UTF-8 + broadcastIntent.putExtra(EXTRA_BYTES_MESSAGE, message) context.sendBroadcast(broadcastIntent) } diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index d09a3e2..bf60641 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -13,6 +13,7 @@ import android.os.PowerManager import android.provider.OpenableColumns import android.text.Editable import android.text.TextWatcher +import android.util.Base64 import android.util.TypedValue import android.view.View import android.view.Window @@ -22,6 +23,7 @@ import io.heckel.ntfy.R import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription +import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64 import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody @@ -110,21 +112,45 @@ fun unmatchedTags(tags: List): List { /** * Prepend tags/emojis to message, but only if there is a non-empty title. - * Otherwise the tags will be prepended to the title. + * Otherwise, the tags will be prepended to the title. */ fun formatMessage(notification: Notification): String { return if (notification.title != "") { - notification.message + decodeMessage(notification) } else { val emojis = toEmojis(splitTags(notification.tags)) if (emojis.isEmpty()) { - notification.message + decodeMessage(notification) } else { - emojis.joinToString("") + " " + notification.message + emojis.joinToString("") + " " + decodeMessage(notification) } } } +fun decodeMessage(notification: Notification): String { + return try { + if (notification.encoding == MESSAGE_ENCODING_BASE64) { + String(Base64.decode(notification.message, Base64.DEFAULT)) + } else { + notification.message + } + } catch (e: IllegalArgumentException) { + notification.message + "(invalid base64)" + } +} + +fun decodeBytesMessage(notification: Notification): ByteArray { + return try { + if (notification.encoding == MESSAGE_ENCODING_BASE64) { + Base64.decode(notification.message, Base64.DEFAULT) + } else { + notification.message.toByteArray() + } + } catch (e: IllegalArgumentException) { + notification.message.toByteArray() + } +} + /** * See above; prepend emojis to title if the title is non-empty. * Otherwise, they are prepended to the message. diff --git a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt index 705330e..2506cee 100644 --- a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt +++ b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt @@ -112,11 +112,6 @@ class FirebaseService : FirebaseMessagingService() { } // Add notification - val decodedMessage = if (encoding == MESSAGE_ENCODING_BASE64) { - String(Base64.decode(message, Base64.DEFAULT)) - } else { - message - } val attachment = if (attachmentUrl != null) { Attachment( name = attachmentName, @@ -131,7 +126,8 @@ class FirebaseService : FirebaseMessagingService() { subscriptionId = subscription.id, timestamp = timestamp, title = title ?: "", - message = decodedMessage, + message = message, + encoding = encoding ?: "", priority = toPriority(priority), tags = tags ?: "", click = click ?: "", diff --git a/fastlane/metadata/android/en-US/changelog/24.txt b/fastlane/metadata/android/en-US/changelog/24.txt new file mode 100644 index 0000000..3e06f82 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelog/24.txt @@ -0,0 +1,2 @@ +Features: +* Support for UnifiedPush 2.0 specification (bytes messages, #130)