A Kotlin Multiplatform library for working with Nostr, with support for JVM, Android, Linux, MacOS/iOS.
Note: This is still in development and very incomplete.
- An introduction or description of Nostr can be found here.
- The Nostr protocol specs can be found here.
Inside your root-level build.gradle(.kts)
file, you should add jitpack
:
// build.gradle.kts
allprojects {
repositories {
// ...
maven { setUrl("https://jitpack.io") }
}
// ...
}
or
// build.gradle
allprojects {
repositories {
// ...
maven { url "https://jitpack.io" }
}
// ...
}
In newer projects, you need to also update the settings.gradle(.kts)
file's dependencyResolutionManagement
block:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' } // <--
jcenter() // Warning: this repository is going to shut down soon
}
}
then, in your module's build.gradle(.kts)
, you need to add:
// build.gradle.kts
dependencies {
//...
implementation("com.github.KotlinGeekDev.kostr:kostr-core:v1.0-beta-06")
}
If you're including it in an Android app, you can just add:
// app/build.gradle.kts
dependencies {
//...
implementation("com.github.KotlinGeekDev.kostr:kostr-core-android:v1.0-beta-06")
}
When publishing an event, or making a subscription/close request to a relay,
ClientMessage
is used to encode the request/event,
and anything sent by a relay is encoded as a RelayMessage
.
RelayPool
,
and actual communication with relays is done with the NostrService
.
You can setup the NostrService with/without a custom relay pool as follows:
// With a custom relay pool.
val requestRelays = RelayPool.fromUrls("wss://relay1", "wss://relay2")
val clientService = NostrService(
relayPool = requestRelays
)
//Without a custom pool(using the default pool)
val service = NostrService()
Note that if you need to do anything custom, such as using read-only relays, you will need to setup the list of relays, then use them in the relay pool:
val customRelays = listOf(
Relay("wss://relay1", readPolicy = true, writePolicy = false), // <-- A relay with custom read/write policy.
Relay("wss://relay2"),
)
val customPool = RelayPool(relays = customRelays)
In order to make a subscription request, you need to construct a RequestMessage
.
And to do that, you need to pass in a subscriptionId(just a string), and a NostrFilter
:
val postsByFiatjafFilter = NostrFilter.newFilter()
.kinds(listOf(EventKind.TEXT_NOTE.kind)) // <-- Looking for posts. Other kinds can be added
.authors(listOf("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d")) // <-- The profiles for which we want to find the posts(as indicated by .kinds() above)
.limit(20) // <-- Setting a limit is important, to avoid issues with relays
.build()
val myRequest = RequestMessage(
subscriptionId = "fiatjaf_posts",
filters = listOf(postsByFiatjafFilter)
)
Now, you can use the NostrService
to make the request, either using request()
or requestWithResult()
.
They are both suspend
functions, and as such, should be called within the appropriate context.
requestWithResult
terminates the connection(s) after receiving all the expected messages. This behaviour could be
modified in the future.
The request()
function has a set of callbacks which are used to handle the incoming messages,
onRelayMessage: suspend (Relay, RelayMessage)
, as well as callbacks for handling errors, onRequestError(Relay, Throwable)
.
An example is given below:
// Example coroutine scope
val appScope = CoroutineScope(Dispatchers.IO)
//Using request() --
appScope.launch {
clientService.request(
myRequest,
onRequestError = { relay, throwable -> handleError(relay, throwable) }
) { relay: Relay, received: RelayMessage -> // This is a suspend callback, so suspend functions can be used here.
useMessage(relay, received)
}
}
The requestWithResult()
function returns a list of events(List<Event>
), no matter the errors that occur
during its execution. The function however displays the errors using standard output. This may be changed in the future.
//Using requestWithResult() ---
// - Assuming a suspending context:
val events = clientService.requestWithResult(myRequest)
// OR, following with the request() example above:
appScope.launch {
val events = clientService.requestWithResult(myRequest)
}
MIT License
Copyright (c) 2022 KotlinGeekDev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.