Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat Suggestion methods api #291

Merged
merged 19 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package social.bigbone.rx

import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single
import social.bigbone.MastodonClient
import social.bigbone.api.entity.Suggestion
import social.bigbone.api.method.SuggestionMethods
import social.bigbone.rx.extensions.onErrorIfNotDisposed

/**
* Reactive implementation of [SuggestionMethods].
* Allows access to API methods with endpoints having an "api/vX/suggestions" prefix.
* @see <a href="https://docs.joinmastodon.org/methods/suggestions/">Mastodon suggestions API methods</a>
*/
class RxSuggestionMethods(client: MastodonClient) {

private val suggestionMethods = SuggestionMethods(client)

@JvmOverloads
fun getSuggestions(limit: Int? = null): Single<List<Suggestion>> {
G10xy marked this conversation as resolved.
Show resolved Hide resolved
return Single.create {
try {
val suggestion = suggestionMethods.getSuggestions(limit)
it.onSuccess(suggestion.execute())
} catch (e: Throwable) {
it.onError(e)
}
}
}

fun removeSuggestion(accountId: String): Completable {
return Completable.create {
try {
suggestionMethods.removeSuggestion(accountId)
it.onComplete()
} catch (throwable: Throwable) {
it.onErrorIfNotDisposed(throwable)
}
}
}
}
8 changes: 8 additions & 0 deletions bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import social.bigbone.api.method.ReportMethods
import social.bigbone.api.method.SearchMethods
import social.bigbone.api.method.StatusMethods
import social.bigbone.api.method.StreamingMethods
import social.bigbone.api.method.SuggestionMethods
import social.bigbone.api.method.TagMethods
import social.bigbone.api.method.TimelineMethods
import social.bigbone.extension.emptyRequestBody
Expand Down Expand Up @@ -252,6 +253,13 @@ private constructor(
@get:JvmName("timelines")
val timelines: TimelineMethods by lazy { TimelineMethods(this) }

/**
* Access API methods under "api/vX/suggestions" endpoint.
*/
@Suppress("unused") // public API
@get:JvmName("suggestions")
val suggestions: SuggestionMethods by lazy { SuggestionMethods(this) }

/**
* Specifies the HTTP methods / HTTP verb that can be used by this class.
*/
Expand Down
50 changes: 50 additions & 0 deletions bigbone/src/main/kotlin/social/bigbone/api/entity/Suggestion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package social.bigbone.api.entity

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
* Represents a suggested account to follow and an associated reason for the suggestion.
* @see <a href="https://docs.joinmastodon.org/entities/Suggestion/">Mastodon API Suggestion</a>
*/

@Serializable
data class Suggestion(

/**
* The reason this account is being suggested.
*/
@SerialName("source")
val source: SourceSuggestion = SourceSuggestion.STAFF,

/**
* The account being recommended to follow.
*/
@SerialName("account")
val account: Account
) {

/**
* Represents a suggested account to follow and an associated reason for the suggestion.
*/
@Serializable
enum class SourceSuggestion {
/**
* This account was manually recommended by your administration team.
*/
@SerialName("staff")
STAFF,

/**
* You have interacted with this account previously.
*/
@SerialName("past_interactions")
PAST_INTERACTIONS,

/**
* This account has many reblogs, favourites, and active local followers within the last 30 days.
*/
@SerialName("global")
GLOBAL
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package social.bigbone.api.method

import social.bigbone.MastodonClient
import social.bigbone.MastodonRequest
import social.bigbone.Parameters
import social.bigbone.api.entity.Suggestion

/**
* Allows access to API methods with endpoints having an "api/vX/suggestions" prefix.
* @see <a href="https://docs.joinmastodon.org/methods/suggestions/">Mastodon API documentation: methods/suggestions/</a>
*/
class SuggestionMethods(private val client: MastodonClient) {

private val suggestionsEndpointV2 = "/api/v2/suggestions"
private val suggestionsEndpointV1 = "/api/v1/suggestions"

/**
* Accounts that are promoted by staff, or that the user has had past positive interactions with, but is not yet following.
* @param limit to limit number of results
* @see <a href="https://docs.joinmastodon.org/methods/suggestions/#v2">Mastodon API documentation: methods/suggestions/#v2</a>
*/
fun getSuggestions(limit: Int? = null): MastodonRequest<List<Suggestion>> {
if (limit != null && (limit <= 0 || limit > QUERY_RESULT_LIMIT)) {
throw IllegalArgumentException("Limit param must be greater than zero and not be higher than $QUERY_RESULT_LIMIT but was $limit")
}
return client.getMastodonRequestForList(
endpoint = suggestionsEndpointV2,
method = MastodonClient.Method.GET,
parameters = Parameters().apply {
append("limit", limit ?: 40)
}
)
}

/**
* Remove an account from follow suggestions.
* @param accountId of the account of interest
* @see <a href="https://docs.joinmastodon.org/methods/suggestions/#v1">Mastodon API documentation: methods/suggestions/#v1</a>
*/
fun removeSuggestion(accountId: String) {
client.performAction(
endpoint = "$suggestionsEndpointV1/$accountId",
method = MastodonClient.Method.DELETE
)
}

companion object {
private const val QUERY_RESULT_LIMIT = 80
}
}
18 changes: 18 additions & 0 deletions bigbone/src/test/assets/suggestions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"source": "past_interactions",
"account": {
"id": "784058",
"username": "katie",
"acct": "[email protected]"
}
},
{
"source": "global",
"account": {
"id": "108194863260762493",
"username": "thunderbird",
"acct": "[email protected]"
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package social.bigbone.api.method

import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldHaveSize
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import social.bigbone.api.entity.Suggestion.SourceSuggestion
import social.bigbone.api.exception.BigBoneRequestException
import social.bigbone.testtool.MockClient

class SuggestionMethodsTest {

@Test
fun getSuggestions() {
val client = MockClient.mock("suggestions.json")
val suggestionMethods = SuggestionMethods(client)
val suggestions = suggestionMethods.getSuggestions(2).execute()
suggestions shouldHaveSize 2
suggestions[0].source shouldBeEqualTo SourceSuggestion.PAST_INTERACTIONS
suggestions[1].source shouldBeEqualTo SourceSuggestion.GLOBAL
}

@Test
fun removeSuggestionWithException() {
Assertions.assertThrows(BigBoneRequestException::class.java) {
val client = MockClient.ioException()
val suggestionMethods = SuggestionMethods(client)
suggestionMethods.removeSuggestion("accountId")
}
}

@Test
fun overRangedLimitValueThrowsException() {
Assertions.assertThrows(IllegalArgumentException::class.java) {
val client = MockClient.ioException()
val suggestionMethods = SuggestionMethods(client)
suggestionMethods.getSuggestions(100).execute()
}
}
}