Skip to content

Commit

Permalink
Merge pull request #35 from capcom6/issue/33-delay-between-messages
Browse files Browse the repository at this point in the history
Delay between messages
  • Loading branch information
capcom6 authored Feb 6, 2024
2 parents 1d992f0 + e8b439f commit bf4ec87
Show file tree
Hide file tree
Showing 36 changed files with 649 additions and 656 deletions.
3 changes: 1 addition & 2 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Android SMS Gateway turns your Android smartphone into an SMS gateway. It's a li
- **Starts at boot:** The application starts running as soon as your device boots up.
- **Multiple SIM cards:** The application supports multiple SIM cards.
- **Multipart messages:** The application supports sending long messages with auto-partitioning.
- **End-to-end encryption:** No one in the middle can access the content and recipients of the
messages.

### Ideal For

Expand Down Expand Up @@ -190,7 +192,7 @@ If you need to send messages with dynamic or shared device IP, you can use the c
- [ ] Incorporate scheduling capabilities for dispatching messages at specific times.
- [ ] Implement region-based restrictions to prevent international SMS.
- [ ] Provide an API endpoint to retrieve the list of available SIM cards on the device.
- [ ] Include detailed error messages in responses and logs.
- [x] Include detailed error messages in responses and logs.

See the [open issues](https://github.com/capcom6/android-sms-gateway/issues) for a full list of proposed features (and known issues).

Expand Down
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.4.2")
implementation("com.google.android.material:material:1.6.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation 'androidx.preference:preference-ktx:1.2.1'

implementation("com.aventrix.jnanoid:jnanoid:2.0.0")

Expand Down
5 changes: 1 addition & 4 deletions app/src/main/java/me/capcom/smsgateway/App.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package me.capcom.smsgateway

import android.app.Application
import androidx.preference.PreferenceManager
import me.capcom.smsgateway.data.dbModule
import me.capcom.smsgateway.modules.encryption.encryptionModule
import me.capcom.smsgateway.modules.gateway.GatewayModule
Expand Down Expand Up @@ -35,8 +34,6 @@ class App: Application() {
EventsReceiver.register(this)
}

val settings by lazy { PreferenceManager.getDefaultSharedPreferences(this) }

val gatewayModule by lazy {
GatewayModule(
get(),
Expand All @@ -46,7 +43,7 @@ class App: Application() {
val localServerModule by lazy {
LocalServerModule(
get(),
PreferencesStorage(settings, "localserver")
PreferencesStorage(get(), "localserver")
)
}

Expand Down
19 changes: 13 additions & 6 deletions app/src/main/java/me/capcom/smsgateway/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.fragment.app.Fragment
import com.google.android.material.tabs.TabLayoutMediator
import me.capcom.smsgateway.databinding.ActivityMainBinding
import me.capcom.smsgateway.ui.HolderFragment
import me.capcom.smsgateway.ui.HomeFragment
import me.capcom.smsgateway.ui.SettingsFragment

class MainActivity : AppCompatActivity() {
Expand All @@ -24,27 +25,33 @@ class MainActivity : AppCompatActivity() {
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
when (position) {
0 -> tab.apply {
text = getString(R.string.tab_text_settings)
setIcon(R.drawable.ic_settings_24)
text = getString(R.string.tab_text_home)
setIcon(R.drawable.ic_home)
}

else -> tab.apply {
1 -> tab.apply {
text = getString(R.string.tab_text_messages)
setIcon(R.drawable.ic_sms)
}

2 -> tab.apply {
text = getString(R.string.tab_text_settings)
setIcon(R.drawable.ic_advanced)
}
}
}.attach()
}

class FragmentsAdapter(activity: AppCompatActivity) :
androidx.viewpager2.adapter.FragmentStateAdapter(activity) {

override fun getItemCount(): Int = 2
override fun getItemCount(): Int = 3

override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> SettingsFragment.newInstance()
else -> HolderFragment.newInstance()
0 -> HomeFragment.newInstance()
1 -> HolderFragment.newInstance()
else -> SettingsFragment.newInstance()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package me.capcom.smsgateway.data.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import me.capcom.smsgateway.modules.messages.data.MessageSource

@Entity
data class Message(
@PrimaryKey val id: String,
val text: String,
@ColumnInfo(defaultValue = "Local")
val source: Source,
val source: MessageSource,
@ColumnInfo(defaultValue = "0")
val isEncrypted: Boolean,
val state: State = State.Pending,
Expand All @@ -24,8 +25,5 @@ data class Message(
Failed,
}

enum class Source {
Local,
Gateway,
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,36 @@ import me.capcom.smsgateway.modules.settings.get
class EncryptionSettings(
private val storage: KeyValueStorage,
) {
var passphrase: String?
val passphrase: String?
get() = storage.get<String>(PASSPHRASE)
set(value) = storage.set(PASSPHRASE, value)

private var version: Int
get() = storage.get<Int>(VERSION) ?: 0
set(value) = storage.set(VERSION, value)

init {
migrate()
}

private fun migrate() {
if (version == VERSION_CODE) {
return
}

if (version < 1) {
passphrase?.let {
storage.set(PASSPHRASE, it)
}
}

version = VERSION_CODE
}

companion object {
private const val VERSION_CODE = 1

private const val PASSPHRASE = "passphrase"

private const val VERSION = "version"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import me.capcom.smsgateway.data.entities.Message
import me.capcom.smsgateway.domain.MessageState
import me.capcom.smsgateway.modules.events.EventBus
import me.capcom.smsgateway.modules.gateway.events.DeviceRegisteredEvent
import me.capcom.smsgateway.modules.messages.MessageStateChangedEvent
import me.capcom.smsgateway.modules.messages.MessagesService
import me.capcom.smsgateway.modules.messages.data.MessageSource
import me.capcom.smsgateway.modules.messages.data.SendParams
import me.capcom.smsgateway.modules.messages.data.SendRequest
import me.capcom.smsgateway.modules.messages.events.MessageStateChangedEvent
import me.capcom.smsgateway.services.PushService

class GatewayModule(
Expand All @@ -38,7 +41,7 @@ class GatewayModule(
withContext(Dispatchers.IO) {
messagesService.events.events.collect { event ->
val event = event as? MessageStateChangedEvent ?: return@collect
if (event.source != Message.Source.Gateway) return@collect
if (event.source != MessageSource.Gateway) return@collect

try {
sendState(event)
Expand Down Expand Up @@ -126,41 +129,50 @@ class GatewayModule(

internal suspend fun getNewMessages() {
val settings = settings.registrationInfo ?: return
withContext(Dispatchers.IO) {
api.getMessages(settings.token)
.forEach {
try {
messagesService.getMessage(it.id)
?.also {
sendState(
MessageStateChangedEvent(
it.message.id,
it.message.state,
it.message.source,
it.recipients.map { rcp ->
MessageStateChangedEvent.Recipient(
rcp.phoneNumber,
rcp.state,
rcp.error
)
}
)
)
}
?: messagesService.sendMessage(
it.id,
it.message,
it.phoneNumbers,
Message.Source.Gateway,
it.simNumber?.let { it - 1 },
it.withDeliveryReport,
it.isEncrypted ?: false,
)
} catch (th: Throwable) {
th.printStackTrace()
val messages = api.getMessages(settings.token)
for (message in messages) {
try {
processMessage(message)
} catch (th: Throwable) {
th.printStackTrace()
}
}
}

private suspend fun processMessage(message: GatewayApi.Message) {
val messageState = messagesService.getMessage(message.id)
if (messageState != null) {
sendState(
MessageStateChangedEvent(
messageState.message.id,
messageState.message.state,
messageState.message.source,
messageState.recipients.map { rcp ->
MessageStateChangedEvent.Recipient(
rcp.phoneNumber,
rcp.state,
rcp.error
)
}
}
)
)
return
}

val request = SendRequest(
MessageSource.Gateway,
me.capcom.smsgateway.modules.messages.data.Message(
message.id,
message.message,
message.phoneNumbers,
message.isEncrypted ?: false
),
SendParams(
message.withDeliveryReport ?: true,
message.simNumber
)
)
messagesService.enqueueMessage(request)
}

private fun Message.State.toApiState(): MessageState = when (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkRequest
import androidx.work.WorkerParameters
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import me.capcom.smsgateway.App
import java.util.concurrent.TimeUnit

Expand All @@ -22,7 +24,7 @@ class PullMessagesWorker(
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
try {
App.instance.gatewayModule.getNewMessages()
withContext(Dispatchers.IO) { App.instance.gatewayModule.getNewMessages() }
return Result.success()
} catch (th: Throwable) {
th.printStackTrace()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.os.PowerManager
import androidx.core.app.NotificationCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.aventrix.jnanoid.jnanoid.NanoIdUtils
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
Expand Down Expand Up @@ -38,6 +39,8 @@ import me.capcom.smsgateway.helpers.SettingsHelper
import me.capcom.smsgateway.modules.localserver.domain.PostMessageRequest
import me.capcom.smsgateway.modules.localserver.domain.PostMessageResponse
import me.capcom.smsgateway.modules.messages.MessagesService
import me.capcom.smsgateway.modules.messages.data.MessageSource
import me.capcom.smsgateway.modules.messages.data.SendRequest
import org.koin.android.ext.android.inject
import kotlin.concurrent.thread

Expand Down Expand Up @@ -107,38 +110,48 @@ class WebService : Service() {
)
}

val message = try {
messagesService.sendMessage(
request.id,
request.message,
request.phoneNumbers,
Message.Source.Local,
request.simNumber?.let { it - 1 },
request.withDeliveryReport,
request.isEncrypted ?: false,
val messageId = try {
val sendRequest = SendRequest(
MessageSource.Local,
me.capcom.smsgateway.modules.messages.data.Message(
request.id ?: NanoIdUtils.randomNanoId(),
request.message,
request.phoneNumbers,
request.isEncrypted ?: false
),
me.capcom.smsgateway.modules.messages.data.SendParams(
request.withDeliveryReport ?: true,
request.simNumber
)
)
messagesService.enqueueMessage(sendRequest)

sendRequest.message.id
} catch (e: IllegalArgumentException) {
return@post call.respond(
HttpStatusCode.BadRequest,
mapOf("message" to e.message)
)
} catch (e: Throwable) {
return@post call.respond(HttpStatusCode.InternalServerError, mapOf("message" to e.message))
return@post call.respond(
HttpStatusCode.InternalServerError,
mapOf("message" to e.message)
)
}

call.respond(
HttpStatusCode.Created,
HttpStatusCode.Accepted,
PostMessageResponse(
id = message.message.id,
state = message.message.state.toApiState(),
recipients = message.recipients.map {
id = messageId,
state = MessageState.Pending,
recipients = request.phoneNumbers.map {
PostMessageResponse.Recipient(
it.phoneNumber,
it.state.toApiState(),
it.error
it,
MessageState.Pending,
null
)
},
isEncrypted = message.message.isEncrypted
isEncrypted = request.isEncrypted ?: false
)
)
}
Expand Down
Loading

0 comments on commit bf4ec87

Please sign in to comment.