Skip to content

Commit

Permalink
First implementation of multiple beatmap mirror support
Browse files Browse the repository at this point in the history
  • Loading branch information
Reco1I committed Dec 13, 2024
1 parent 1e1fb4b commit 8e07328
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 104 deletions.
32 changes: 16 additions & 16 deletions src/com/reco1l/osu/beatmaplisting/BeatmapListing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -195,21 +195,17 @@ class BeatmapListing : BaseFragment(),

ensureActive()

JsonArrayRequest(mirror.search.endpoint).use { request ->

request.buildUrl {

addQueryParameter("mode", "0")
addQueryParameter("query", searchBox.text.toString())
addQueryParameter("offset", offset.toString())
}
JsonArrayRequest(
mirror.search.request(
query = searchBox.text.toString(),
offset = offset
)
).use { request ->

request.buildRequest { header("User-Agent", "Chrome/Android") }

ensureActive()

val beatmapSets = mirror.search.mapResponse(request.execute().json)

val beatmapSets = mirror.search.response(request.execute().json)
ensureActive()

adapter.data.addAll(beatmapSets)
Expand Down Expand Up @@ -393,8 +389,10 @@ class BeatmapSetDetails(val beatmapSet: BeatmapSetModel, val holder: BeatmapSetV
}

downloadButton.setOnClickListener {
val url = BeatmapListing.mirror.downloadEndpoint(beatmapSet.id)
BeatmapDownloader.download(url, "${beatmapSet.id} ${beatmapSet.artist} - ${beatmapSet.title}")
BeatmapDownloader.download(
url = BeatmapListing.mirror.download.request(beatmapSet.id).toString(),
suggestedFilename = "${beatmapSet.id} ${beatmapSet.artist} - ${beatmapSet.title}"
)
}

cover.setImageDrawable(holder.cover.drawable)
Expand Down Expand Up @@ -598,8 +596,10 @@ class BeatmapSetViewHolder(itemView: View, private val mediaScope: CoroutineScop
}

downloadButton.setOnClickListener {
val url = BeatmapListing.mirror.downloadEndpoint(beatmapSet.id)
BeatmapDownloader.download(url, "${beatmapSet.id} ${beatmapSet.artist} - ${beatmapSet.title}")
BeatmapDownloader.download(
url = BeatmapListing.mirror.download.request(beatmapSet.id).toString(),
suggestedFilename = "${beatmapSet.id} ${beatmapSet.artist} - ${beatmapSet.title}"
)
}


Expand All @@ -621,7 +621,7 @@ class BeatmapSetViewHolder(itemView: View, private val mediaScope: CoroutineScop
previewJob = mediaScope.launch {

try {
previewStream = URLBassStream(BeatmapListing.mirror.previewEndpoint(beatmapSet.beatmaps[0].id)) {
previewStream = URLBassStream(BeatmapListing.mirror.preview.request(beatmapSet.beatmaps[0].id).toString()) {
stopPreview(true)

if (BeatmapListing.isPlayingMusic) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,31 @@ import ru.nsu.ccfit.zuev.osu.RankedStatus
* Beatmap set response model for beatmaps mirrors.
*/
data class BeatmapSetModel(

val id: Long,

val title: String,

val titleUnicode: String,

val artist: String,

val artistUnicode: String,

val status: RankedStatus,

val creator: String,

val thumbnail: String?,

val beatmaps: List<BeatmapModel>

)

/**
* Beatmap response model for beatmaps mirrors.
*/
data class BeatmapModel(

val id: Long,

val version: String,

val starRating: Double,

val ar: Double,

val cs: Double,

val hp: Double,

val od: Double,

val bpm: Double,

val lengthSec: Long,

val circleCount: Int,

val sliderCount:Int,

val spinnerCount: Int

)
104 changes: 39 additions & 65 deletions src/com/reco1l/osu/beatmaplisting/BeatmapMirror.kt
Original file line number Diff line number Diff line change
@@ -1,97 +1,71 @@
package com.reco1l.osu.beatmaplisting

import org.json.JSONArray
import ru.nsu.ccfit.zuev.osu.RankedStatus

import androidx.annotation.DrawableRes
import com.reco1l.osu.beatmaplisting.mirrors.OsuDirectDownloadRequestModel
import com.reco1l.osu.beatmaplisting.mirrors.OsuDirectPreviewRequestModel
import com.reco1l.osu.beatmaplisting.mirrors.OsuDirectSearchRequestModel
import com.reco1l.osu.beatmaplisting.mirrors.OsuDirectSearchResponseModel
import ru.nsu.ccfit.zuev.osuplus.R

/**
* Defines an action to be performed on a mirror API.
* Defines a beatmap mirror API and its actions.
*/
data class MirrorAction<R, M>(
enum class BeatmapMirror(

/**
* The action API endpoint.
* The home URL of the beatmap mirror where the user will be redirected to when
* clicking on the logo.
*/
// TODO replace with a request creation function, some APIs have different query arguments.
val endpoint: String,
val homeUrl: String,

/**
* A function to map the response into a model.
* The description of the beatmap mirror.
*/
val mapResponse: (R) -> M
val description: String,

)
/**
* The resource ID of the logo image to be displayed in the UI.
*/
@DrawableRes
val logoResource: Int,

/**
* Defines a beatmap mirror API and its actions.
*/
enum class BeatmapMirror(

// Actions / Endpoints

/**
* The search query action.
*/
val search: MirrorAction<JSONArray, MutableList<BeatmapSetModel>>,
val search: BeatmapMirrorActionWithResponse<BeatmapMirrorSearchRequestModel, BeatmapMirrorSearchResponseModel>,

val downloadEndpoint: (Long) -> String,
/**
* The download action.
*/
val download: BeatmapMirrorAction<BeatmapMirrorDownloadRequestModel>,

val previewEndpoint: (Long) -> String,
/**
* The music preview action.
*/
val preview: BeatmapMirrorAction<BeatmapMirrorPreviewRequestModel>,

) {
) {

/**
* osu.direct beatmap mirror.
*
* [See documentation](https://osu.direct/api/docs)
*/
OSU_DIRECT(
search = MirrorAction(
endpoint = "https://osu.direct/api/v2/search",
mapResponse = { array ->

MutableList(array.length()) { index ->

val json = array.getJSONObject(index)

BeatmapSetModel(
id = json.getLong("id"),
title = json.getString("title"),
titleUnicode = json.getString("title_unicode"),
artist = json.getString("artist"),
artistUnicode = json.getString("artist_unicode"),
status = RankedStatus.valueOf(json.getInt("ranked")),
creator = json.getString("creator"),
thumbnail = json.optJSONObject("covers")?.optString("card"),
beatmaps = json.getJSONArray("beatmaps").let {

MutableList(it.length()) { i ->

val obj = it.getJSONObject(i)

BeatmapModel(
id = obj.getLong("id"),
version = obj.getString("version"),
starRating = obj.getDouble("difficulty_rating"),
ar = obj.getDouble("ar"),
cs = obj.getDouble("cs"),
hp = obj.getDouble("drain"),
od = obj.getDouble("accuracy"),
bpm = obj.getDouble("bpm"),
lengthSec = obj.getLong("hit_length"),
circleCount = obj.getInt("count_circles"),
sliderCount = obj.getInt("count_sliders"),
spinnerCount = obj.getInt("count_spinners")
)

}.sortedBy(BeatmapModel::starRating)
}
)
}
homeUrl = "https://osu.direct",
description = "osu.direct",
logoResource = R.drawable.powered_by_osudirect,

}
search = BeatmapMirrorActionWithResponse(
request = OsuDirectSearchRequestModel(),
response = OsuDirectSearchResponseModel(),
),
downloadEndpoint = { "https://osu.direct/api/d/$it" },
previewEndpoint = { "https://osu.direct/api/media/preview/$it" },
);
download = BeatmapMirrorAction(OsuDirectDownloadRequestModel()),
preview = BeatmapMirrorAction(OsuDirectPreviewRequestModel()),
)

}

24 changes: 24 additions & 0 deletions src/com/reco1l/osu/beatmaplisting/BeatmapMirrorAction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.reco1l.osu.beatmaplisting

/**
* Defines an action to be performed on a mirror API.
*/
open class BeatmapMirrorAction<RequestModel>(

/**
* The action API endpoint.
*/
val request: RequestModel

)

class BeatmapMirrorActionWithResponse<RequestModel, ResponseModel>(

request: RequestModel,

/**
* The action response mapping.
*/
val response: ResponseModel

) : BeatmapMirrorAction<RequestModel>(request)
38 changes: 38 additions & 0 deletions src/com/reco1l/osu/beatmaplisting/BeatmapMirrorModels.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.reco1l.osu.beatmaplisting

import okhttp3.HttpUrl


// Request

fun interface BeatmapMirrorSearchRequestModel {
/**
* @param query The search query.
* @param offset The search result offset.
*/
operator fun invoke(query: String, offset: Int): HttpUrl
}

fun interface BeatmapMirrorDownloadRequestModel {
/**
* @param beatmapSetId The beatmap set ID.
*/
operator fun invoke(beatmapSetId: Long): HttpUrl
}

fun interface BeatmapMirrorPreviewRequestModel {
/**
* @param beatmapSetId The beatmap set ID.
*/
operator fun invoke(beatmapSetId: Long): HttpUrl
}


// Response

fun interface BeatmapMirrorSearchResponseModel {
/**
* @return The list of beatmap sets.
*/
operator fun invoke(response: Any): MutableList<BeatmapSetModel>
}
Loading

0 comments on commit 8e07328

Please sign in to comment.