Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 38e997cba2 |
@ -26,12 +26,8 @@ val IDX_REPLAY_IDS_PAIRS: Index = Internal.createIndex(DSL.name("idx_replay_ids_
|
|||||||
val IDX_SCORES_BEATMAP_ID: Index = Internal.createIndex(DSL.name("idx_scores_beatmap_id"), Scores.SCORES, arrayOf(Scores.SCORES.BEATMAP_ID), false)
|
val IDX_SCORES_BEATMAP_ID: Index = Internal.createIndex(DSL.name("idx_scores_beatmap_id"), Scores.SCORES, arrayOf(Scores.SCORES.BEATMAP_ID), false)
|
||||||
val IDX_SCORES_BEATMAP_ID_REPLAY_ID: Index = Internal.createIndex(DSL.name("idx_scores_beatmap_id_replay_id"), Scores.SCORES, arrayOf(Scores.SCORES.BEATMAP_ID, Scores.SCORES.REPLAY_ID), false)
|
val IDX_SCORES_BEATMAP_ID_REPLAY_ID: Index = Internal.createIndex(DSL.name("idx_scores_beatmap_id_replay_id"), Scores.SCORES, arrayOf(Scores.SCORES.BEATMAP_ID, Scores.SCORES.REPLAY_ID), false)
|
||||||
val IDX_SCORES_BEATMAP_ID_REPLAY_ID_UR: Index = Internal.createIndex(DSL.name("idx_scores_beatmap_id_replay_id_ur"), Scores.SCORES, arrayOf(Scores.SCORES.BEATMAP_ID, Scores.SCORES.REPLAY_ID, Scores.SCORES.UR), false)
|
val IDX_SCORES_BEATMAP_ID_REPLAY_ID_UR: Index = Internal.createIndex(DSL.name("idx_scores_beatmap_id_replay_id_ur"), Scores.SCORES, arrayOf(Scores.SCORES.BEATMAP_ID, Scores.SCORES.REPLAY_ID, Scores.SCORES.UR), false)
|
||||||
val IDX_SCORES_IS_BANNED: Index = Internal.createIndex(DSL.name("idx_scores_is_banned"), Scores.SCORES, arrayOf(Scores.SCORES.IS_BANNED), false)
|
|
||||||
val IDX_SCORES_JUDGEMENTS_SCORE_ID: Index = Internal.createIndex(DSL.name("idx_scores_judgements_score_id"), ScoresJudgements.SCORES_JUDGEMENTS, arrayOf(ScoresJudgements.SCORES_JUDGEMENTS.SCORE_ID), false)
|
val IDX_SCORES_JUDGEMENTS_SCORE_ID: Index = Internal.createIndex(DSL.name("idx_scores_judgements_score_id"), ScoresJudgements.SCORES_JUDGEMENTS, arrayOf(ScoresJudgements.SCORES_JUDGEMENTS.SCORE_ID), false)
|
||||||
val IDX_SCORES_KEYPRESSES_STANDARD_DEVIATION_ADJUSTED: Index = Internal.createIndex(DSL.name("idx_scores_keypresses_standard_deviation_adjusted"), Scores.SCORES, arrayOf(Scores.SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED), false)
|
|
||||||
val IDX_SCORES_PP: Index = Internal.createIndex(DSL.name("idx_scores_pp"), Scores.SCORES, arrayOf(Scores.SCORES.PP), false)
|
|
||||||
val IDX_SCORES_REPLAY_ID: Index = Internal.createIndex(DSL.name("idx_scores_replay_id"), Scores.SCORES, arrayOf(Scores.SCORES.REPLAY_ID), false)
|
val IDX_SCORES_REPLAY_ID: Index = Internal.createIndex(DSL.name("idx_scores_replay_id"), Scores.SCORES, arrayOf(Scores.SCORES.REPLAY_ID), false)
|
||||||
val IDX_SCORES_SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED: Index = Internal.createIndex(DSL.name("idx_scores_sliderend_release_standard_deviation_adjusted"), Scores.SCORES, arrayOf(Scores.SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED), false)
|
|
||||||
val IDX_SCORES_UR: Index = Internal.createIndex(DSL.name("idx_scores_ur"), Scores.SCORES, arrayOf(Scores.SCORES.UR), false)
|
val IDX_SCORES_UR: Index = Internal.createIndex(DSL.name("idx_scores_ur"), Scores.SCORES, arrayOf(Scores.SCORES.UR), false)
|
||||||
val IDX_SCORES_USER_ID: Index = Internal.createIndex(DSL.name("idx_scores_user_id"), Scores.SCORES, arrayOf(Scores.SCORES.USER_ID), false)
|
val IDX_SCORES_USER_ID: Index = Internal.createIndex(DSL.name("idx_scores_user_id"), Scores.SCORES, arrayOf(Scores.SCORES.USER_ID), false)
|
||||||
val IDX_USERS_IS_BANNED_FALSE: Index = Internal.createIndex(DSL.name("idx_users_is_banned_false"), Users.USERS, arrayOf(Users.USERS.IS_BANNED), false)
|
val IDX_USERS_IS_BANNED_FALSE: Index = Internal.createIndex(DSL.name("idx_users_is_banned_false"), Users.USERS, arrayOf(Users.USERS.IS_BANNED), false)
|
||||||
|
|||||||
@ -8,11 +8,7 @@ import com.nisemoe.generated.Public
|
|||||||
import com.nisemoe.generated.indexes.IDX_SCORES_BEATMAP_ID
|
import com.nisemoe.generated.indexes.IDX_SCORES_BEATMAP_ID
|
||||||
import com.nisemoe.generated.indexes.IDX_SCORES_BEATMAP_ID_REPLAY_ID
|
import com.nisemoe.generated.indexes.IDX_SCORES_BEATMAP_ID_REPLAY_ID
|
||||||
import com.nisemoe.generated.indexes.IDX_SCORES_BEATMAP_ID_REPLAY_ID_UR
|
import com.nisemoe.generated.indexes.IDX_SCORES_BEATMAP_ID_REPLAY_ID_UR
|
||||||
import com.nisemoe.generated.indexes.IDX_SCORES_IS_BANNED
|
|
||||||
import com.nisemoe.generated.indexes.IDX_SCORES_KEYPRESSES_STANDARD_DEVIATION_ADJUSTED
|
|
||||||
import com.nisemoe.generated.indexes.IDX_SCORES_PP
|
|
||||||
import com.nisemoe.generated.indexes.IDX_SCORES_REPLAY_ID
|
import com.nisemoe.generated.indexes.IDX_SCORES_REPLAY_ID
|
||||||
import com.nisemoe.generated.indexes.IDX_SCORES_SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED
|
|
||||||
import com.nisemoe.generated.indexes.IDX_SCORES_UR
|
import com.nisemoe.generated.indexes.IDX_SCORES_UR
|
||||||
import com.nisemoe.generated.indexes.IDX_SCORES_USER_ID
|
import com.nisemoe.generated.indexes.IDX_SCORES_USER_ID
|
||||||
import com.nisemoe.generated.keys.REPLAY_ID_UNIQUE
|
import com.nisemoe.generated.keys.REPLAY_ID_UNIQUE
|
||||||
@ -321,11 +317,6 @@ open class Scores(
|
|||||||
*/
|
*/
|
||||||
val JUDGEMENTS: TableField<ScoresRecord, ByteArray?> = createField(DSL.name("judgements"), SQLDataType.BLOB, this, "")
|
val JUDGEMENTS: TableField<ScoresRecord, ByteArray?> = createField(DSL.name("judgements"), SQLDataType.BLOB, this, "")
|
||||||
|
|
||||||
/**
|
|
||||||
* The column <code>public.scores.leaderboard_rank</code>.
|
|
||||||
*/
|
|
||||||
val LEADERBOARD_RANK: TableField<ScoresRecord, Long?> = createField(DSL.name("leaderboard_rank"), SQLDataType.BIGINT, this, "")
|
|
||||||
|
|
||||||
private constructor(alias: Name, aliased: Table<ScoresRecord>?): this(alias, null, null, null, aliased, null, null)
|
private constructor(alias: Name, aliased: Table<ScoresRecord>?): this(alias, null, null, null, aliased, null, null)
|
||||||
private constructor(alias: Name, aliased: Table<ScoresRecord>?, parameters: Array<Field<*>?>?): this(alias, null, null, null, aliased, parameters, null)
|
private constructor(alias: Name, aliased: Table<ScoresRecord>?, parameters: Array<Field<*>?>?): this(alias, null, null, null, aliased, parameters, null)
|
||||||
private constructor(alias: Name, aliased: Table<ScoresRecord>?, where: Condition?): this(alias, null, null, null, aliased, null, where)
|
private constructor(alias: Name, aliased: Table<ScoresRecord>?, where: Condition?): this(alias, null, null, null, aliased, null, where)
|
||||||
@ -358,7 +349,7 @@ open class Scores(
|
|||||||
override fun `as`(alias: Table<*>): ScoresPath = ScoresPath(alias.qualifiedName, this)
|
override fun `as`(alias: Table<*>): ScoresPath = ScoresPath(alias.qualifiedName, this)
|
||||||
}
|
}
|
||||||
override fun getSchema(): Schema? = if (aliased()) null else Public.PUBLIC
|
override fun getSchema(): Schema? = if (aliased()) null else Public.PUBLIC
|
||||||
override fun getIndexes(): List<Index> = listOf(IDX_SCORES_BEATMAP_ID, IDX_SCORES_BEATMAP_ID_REPLAY_ID, IDX_SCORES_BEATMAP_ID_REPLAY_ID_UR, IDX_SCORES_IS_BANNED, IDX_SCORES_KEYPRESSES_STANDARD_DEVIATION_ADJUSTED, IDX_SCORES_PP, IDX_SCORES_REPLAY_ID, IDX_SCORES_SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED, IDX_SCORES_UR, IDX_SCORES_USER_ID)
|
override fun getIndexes(): List<Index> = listOf(IDX_SCORES_BEATMAP_ID, IDX_SCORES_BEATMAP_ID_REPLAY_ID, IDX_SCORES_BEATMAP_ID_REPLAY_ID_UR, IDX_SCORES_REPLAY_ID, IDX_SCORES_UR, IDX_SCORES_USER_ID)
|
||||||
override fun getPrimaryKey(): UniqueKey<ScoresRecord> = SCORES_PKEY
|
override fun getPrimaryKey(): UniqueKey<ScoresRecord> = SCORES_PKEY
|
||||||
override fun getUniqueKeys(): List<UniqueKey<ScoresRecord>> = listOf(REPLAY_ID_UNIQUE)
|
override fun getUniqueKeys(): List<UniqueKey<ScoresRecord>> = listOf(REPLAY_ID_UNIQUE)
|
||||||
|
|
||||||
|
|||||||
@ -205,10 +205,6 @@ open class ScoresRecord private constructor() : UpdatableRecordImpl<ScoresRecord
|
|||||||
set(value): Unit = set(45, value)
|
set(value): Unit = set(45, value)
|
||||||
get(): ByteArray? = get(45) as ByteArray?
|
get(): ByteArray? = get(45) as ByteArray?
|
||||||
|
|
||||||
open var leaderboardRank: Long?
|
|
||||||
set(value): Unit = set(46, value)
|
|
||||||
get(): Long? = get(46) as Long?
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Primary key information
|
// Primary key information
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@ -218,7 +214,7 @@ open class ScoresRecord private constructor() : UpdatableRecordImpl<ScoresRecord
|
|||||||
/**
|
/**
|
||||||
* Create a detached, initialised ScoresRecord
|
* Create a detached, initialised ScoresRecord
|
||||||
*/
|
*/
|
||||||
constructor(id: Int? = null, beatmapId: Int? = null, count_100: Int? = null, count_300: Int? = null, count_50: Int? = null, countMiss: Int? = null, date: LocalDateTime? = null, maxCombo: Int? = null, mods: Int? = null, perfect: Boolean? = null, pp: Double? = null, rank: String? = null, replayAvailable: Boolean? = null, replayId: Long? = null, score: Long? = null, userId: Long? = null, replay: ByteArray? = null, ur: Double? = null, frametime: Double? = null, edgeHits: Int? = null, snaps: Int? = null, isBanned: Boolean? = null, adjustedUr: Double? = null, meanError: Double? = null, errorVariance: Double? = null, errorStandardDeviation: Double? = null, minimumError: Double? = null, maximumError: Double? = null, errorRange: Double? = null, errorCoefficientOfVariation: Double? = null, errorKurtosis: Double? = null, errorSkewness: Double? = null, sentDiscordNotification: Boolean? = null, addedAt: OffsetDateTime? = null, version: Int? = null, keypressesTimes: Array<Double?>? = null, keypressesMedian: Double? = null, keypressesStandardDeviation: Double? = null, sliderendReleaseTimes: Array<Double?>? = null, sliderendReleaseMedian: Double? = null, sliderendReleaseStandardDeviation: Double? = null, keypressesMedianAdjusted: Double? = null, keypressesStandardDeviationAdjusted: Double? = null, sliderendReleaseMedianAdjusted: Double? = null, sliderendReleaseStandardDeviationAdjusted: Double? = null, judgements: ByteArray? = null, leaderboardRank: Long? = null): this() {
|
constructor(id: Int? = null, beatmapId: Int? = null, count_100: Int? = null, count_300: Int? = null, count_50: Int? = null, countMiss: Int? = null, date: LocalDateTime? = null, maxCombo: Int? = null, mods: Int? = null, perfect: Boolean? = null, pp: Double? = null, rank: String? = null, replayAvailable: Boolean? = null, replayId: Long? = null, score: Long? = null, userId: Long? = null, replay: ByteArray? = null, ur: Double? = null, frametime: Double? = null, edgeHits: Int? = null, snaps: Int? = null, isBanned: Boolean? = null, adjustedUr: Double? = null, meanError: Double? = null, errorVariance: Double? = null, errorStandardDeviation: Double? = null, minimumError: Double? = null, maximumError: Double? = null, errorRange: Double? = null, errorCoefficientOfVariation: Double? = null, errorKurtosis: Double? = null, errorSkewness: Double? = null, sentDiscordNotification: Boolean? = null, addedAt: OffsetDateTime? = null, version: Int? = null, keypressesTimes: Array<Double?>? = null, keypressesMedian: Double? = null, keypressesStandardDeviation: Double? = null, sliderendReleaseTimes: Array<Double?>? = null, sliderendReleaseMedian: Double? = null, sliderendReleaseStandardDeviation: Double? = null, keypressesMedianAdjusted: Double? = null, keypressesStandardDeviationAdjusted: Double? = null, sliderendReleaseMedianAdjusted: Double? = null, sliderendReleaseStandardDeviationAdjusted: Double? = null, judgements: ByteArray? = null): this() {
|
||||||
this.id = id
|
this.id = id
|
||||||
this.beatmapId = beatmapId
|
this.beatmapId = beatmapId
|
||||||
this.count_100 = count_100
|
this.count_100 = count_100
|
||||||
@ -265,7 +261,6 @@ open class ScoresRecord private constructor() : UpdatableRecordImpl<ScoresRecord
|
|||||||
this.sliderendReleaseMedianAdjusted = sliderendReleaseMedianAdjusted
|
this.sliderendReleaseMedianAdjusted = sliderendReleaseMedianAdjusted
|
||||||
this.sliderendReleaseStandardDeviationAdjusted = sliderendReleaseStandardDeviationAdjusted
|
this.sliderendReleaseStandardDeviationAdjusted = sliderendReleaseStandardDeviationAdjusted
|
||||||
this.judgements = judgements
|
this.judgements = judgements
|
||||||
this.leaderboardRank = leaderboardRank
|
|
||||||
resetChangedOnNotNull()
|
resetChangedOnNotNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,8 +56,7 @@ data class SuspiciousScoreEntry(
|
|||||||
val beatmap_star_rating: Double,
|
val beatmap_star_rating: Double,
|
||||||
val pp: Double,
|
val pp: Double,
|
||||||
val frametime: Double,
|
val frametime: Double,
|
||||||
val ur: Double,
|
val ur: Double
|
||||||
val leaderboard_rank: Long?,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
data class SimilarReplayEntry(
|
data class SimilarReplayEntry(
|
||||||
@ -65,8 +64,6 @@ data class SimilarReplayEntry(
|
|||||||
val replay_id_2: Long,
|
val replay_id_2: Long,
|
||||||
val user_id_1: Long,
|
val user_id_1: Long,
|
||||||
val user_id_2: Long,
|
val user_id_2: Long,
|
||||||
val user_banned_1: Boolean,
|
|
||||||
val user_banned_2: Boolean,
|
|
||||||
val username_1: String,
|
val username_1: String,
|
||||||
val username_2: String,
|
val username_2: String,
|
||||||
val beatmap_beatmapset_id: Long,
|
val beatmap_beatmapset_id: Long,
|
||||||
@ -74,8 +71,6 @@ data class SimilarReplayEntry(
|
|||||||
val replay_date_2: String,
|
val replay_date_2: String,
|
||||||
val replay_pp_1: Double,
|
val replay_pp_1: Double,
|
||||||
val replay_pp_2: Double,
|
val replay_pp_2: Double,
|
||||||
val replay_leaderboard_rank_1: Long?,
|
|
||||||
val replay_leaderboard_rank_2: Long?,
|
|
||||||
val beatmap_id: Long,
|
val beatmap_id: Long,
|
||||||
val beatmap_title: String,
|
val beatmap_title: String,
|
||||||
val beatmap_star_rating: Double,
|
val beatmap_star_rating: Double,
|
||||||
@ -180,7 +175,6 @@ data class ReplayData(
|
|||||||
val pp: Double?,
|
val pp: Double?,
|
||||||
val perfect: Boolean,
|
val perfect: Boolean,
|
||||||
val max_combo: Int,
|
val max_combo: Int,
|
||||||
val leaderboard_rank: Long?,
|
|
||||||
|
|
||||||
val count_300: Int,
|
val count_300: Int,
|
||||||
val count_100: Int,
|
val count_100: Int,
|
||||||
@ -204,31 +198,6 @@ data class ReplayData(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains everything needed to reconstruct a replay (.osr) file.
|
|
||||||
// We currently do not store some of these - these have been marked.
|
|
||||||
data class EncodedReplayData(
|
|
||||||
val gameMode: Byte,
|
|
||||||
val gameVersion: Int,
|
|
||||||
val beatmapHash: String,
|
|
||||||
val username: String,
|
|
||||||
val replayHash: String, // We do not store - maybe we can reconstruct?
|
|
||||||
val count300: Short,
|
|
||||||
val count100: Short,
|
|
||||||
val count50: Short,
|
|
||||||
val countGeki: Short, // We do not store - probably should
|
|
||||||
val countKatu: Short, // We do not store - probably should
|
|
||||||
val countMisses: Short,
|
|
||||||
val totalScore: Int,
|
|
||||||
val greatestCombo: Short,
|
|
||||||
val perfect: Byte,
|
|
||||||
val mods: Int,
|
|
||||||
val lifeBar: String, // We do not store, and maybe shouldn't?
|
|
||||||
val timeStamp: Long,
|
|
||||||
val replayLength: ByteArray, // We do not store - could be calculated?
|
|
||||||
val scoreId: Long,
|
|
||||||
val additionalInformation: Double, // We do not store - probably not needed
|
|
||||||
)
|
|
||||||
|
|
||||||
data class DistributionEntry(
|
data class DistributionEntry(
|
||||||
val countMiss: Double,
|
val countMiss: Double,
|
||||||
val count300: Double,
|
val count300: Double,
|
||||||
|
|||||||
@ -76,7 +76,7 @@ class BanlistController(
|
|||||||
.where(USERS.IS_BANNED.eq(true))
|
.where(USERS.IS_BANNED.eq(true))
|
||||||
.orderBy(USERS.APPROX_BAN_DATE.desc())
|
.orderBy(USERS.APPROX_BAN_DATE.desc())
|
||||||
.limit(MAX_BANLIST_ENTRIES_PER_PAGE)
|
.limit(MAX_BANLIST_ENTRIES_PER_PAGE)
|
||||||
.offset((request.page - 1) * MAX_BANLIST_ENTRIES_PER_PAGE)
|
.offset((request.page - 1) * 10)
|
||||||
.fetch()
|
.fetch()
|
||||||
.map {
|
.map {
|
||||||
BanlistEntry(
|
BanlistEntry(
|
||||||
|
|||||||
@ -8,15 +8,15 @@ data class HealthResponse(
|
|||||||
val healthy: Boolean,
|
val healthy: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
@RestController
|
val healthResponse = HealthResponse(
|
||||||
class HealthController {
|
|
||||||
companion object {
|
|
||||||
val HEALTH = HealthResponse(
|
|
||||||
healthy = true,
|
healthy = true,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
class HealthController {
|
||||||
@GetMapping("/health")
|
@GetMapping("/health")
|
||||||
fun healthCheck(): ResponseEntity<HealthResponse> = ResponseEntity.ok(HEALTH)
|
fun healthCheck(): ResponseEntity<HealthResponse> {
|
||||||
|
return ResponseEntity.ok(healthResponse)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,14 +8,12 @@ data class VersionResponse(
|
|||||||
val version: String,
|
val version: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val versionResponse = VersionResponse(
|
||||||
|
version = "v20250213",
|
||||||
|
)
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
class VersionController {
|
class VersionController {
|
||||||
companion object {
|
|
||||||
val VERSION = VersionResponse(
|
|
||||||
version = "v20250702",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/version")
|
@GetMapping("/version")
|
||||||
fun getVersion(): ResponseEntity<VersionResponse> = ResponseEntity.ok(VERSION)
|
fun getVersion(): ResponseEntity<VersionResponse> = ResponseEntity.ok(versionResponse)
|
||||||
}
|
}
|
||||||
@ -10,7 +10,6 @@ import com.nisemoe.nise.osu.OsuApi
|
|||||||
import com.nisemoe.nise.service.AuthService
|
import com.nisemoe.nise.service.AuthService
|
||||||
import com.nisemoe.nise.service.CompressReplay
|
import com.nisemoe.nise.service.CompressReplay
|
||||||
import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream
|
import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream
|
||||||
import org.jetbrains.kotlinx.dataframe.impl.asList
|
|
||||||
import org.jooq.Condition
|
import org.jooq.Condition
|
||||||
import org.jooq.DSLContext
|
import org.jooq.DSLContext
|
||||||
import org.jooq.Record
|
import org.jooq.Record
|
||||||
@ -40,32 +39,6 @@ class ScoreService(
|
|||||||
val osuUserAlias1 = USERS.`as`("osu_user_alias1")
|
val osuUserAlias1 = USERS.`as`("osu_user_alias1")
|
||||||
val osuUserAlias2 = USERS.`as`("osu_user_alias2")
|
val osuUserAlias2 = USERS.`as`("osu_user_alias2")
|
||||||
|
|
||||||
val SKIPPED_SIMILARITY_MAPS = arrayOf(
|
|
||||||
2635266, // - Death is just the beginning cs10
|
|
||||||
2528044, // - Genkaku Catastrophe cs10
|
|
||||||
2528067, // - Genkaku Catastrophe cs8
|
|
||||||
2665747, // - expand cs9
|
|
||||||
3063865, // - Kagayaku Hari no Kobitozoku ~ Little Princess 700 note stream
|
|
||||||
4641077, // - granat cs10
|
|
||||||
3616081, // - shop cs10
|
|
||||||
3533781, //- uwa cs10
|
|
||||||
3535358, // - uwa cs8
|
|
||||||
3208341, // - mizuumi cs8
|
|
||||||
4871320, // - youre a winner ninerik incident
|
|
||||||
3237399, // - dialtone cs10
|
|
||||||
4383507, // - another diff from the 700 note stream set
|
|
||||||
1606883, // - tabi no tochu cs7
|
|
||||||
4991602, // new bad map
|
|
||||||
4997662, // new bad map
|
|
||||||
1839017,
|
|
||||||
4467121,
|
|
||||||
4783487,
|
|
||||||
3772967,
|
|
||||||
3571032,
|
|
||||||
4443518,
|
|
||||||
5125702,
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCharts(db: Record): List<ReplayDataChart> {
|
fun getCharts(db: Record): List<ReplayDataChart> {
|
||||||
@ -192,8 +165,7 @@ class ScoreService(
|
|||||||
SCORES.ERROR_KURTOSIS,
|
SCORES.ERROR_KURTOSIS,
|
||||||
SCORES.ERROR_SKEWNESS,
|
SCORES.ERROR_SKEWNESS,
|
||||||
SCORES.SLIDEREND_RELEASE_TIMES,
|
SCORES.SLIDEREND_RELEASE_TIMES,
|
||||||
SCORES.KEYPRESSES_TIMES,
|
SCORES.KEYPRESSES_TIMES
|
||||||
SCORES.LEADERBOARD_RANK,
|
|
||||||
)
|
)
|
||||||
.from(SCORES)
|
.from(SCORES)
|
||||||
.join(USERS).on(SCORES.USER_ID.eq(USERS.USER_ID))
|
.join(USERS).on(SCORES.USER_ID.eq(USERS.USER_ID))
|
||||||
@ -256,8 +228,7 @@ class ScoreService(
|
|||||||
error_kurtosis = result.get(SCORES.ERROR_KURTOSIS, Double::class.java),
|
error_kurtosis = result.get(SCORES.ERROR_KURTOSIS, Double::class.java),
|
||||||
error_skewness = result.get(SCORES.ERROR_SKEWNESS, Double::class.java),
|
error_skewness = result.get(SCORES.ERROR_SKEWNESS, Double::class.java),
|
||||||
charts = charts,
|
charts = charts,
|
||||||
similar_scores = this.getSimilarScores(replayId),
|
similar_scores = this.getSimilarScores(replayId)
|
||||||
leaderboard_rank = result.get(SCORES.LEADERBOARD_RANK, Long::class.java)
|
|
||||||
)
|
)
|
||||||
this.loadComparableReplayData(replayData)
|
this.loadComparableReplayData(replayData)
|
||||||
return replayData
|
return replayData
|
||||||
@ -280,8 +251,7 @@ class ScoreService(
|
|||||||
BEATMAPS.STAR_RATING,
|
BEATMAPS.STAR_RATING,
|
||||||
SCORES.PP,
|
SCORES.PP,
|
||||||
SCORES.FRAMETIME,
|
SCORES.FRAMETIME,
|
||||||
SCORES.UR,
|
SCORES.UR
|
||||||
SCORES.LEADERBOARD_RANK,
|
|
||||||
)
|
)
|
||||||
.from(SCORES)
|
.from(SCORES)
|
||||||
.join(USERS).on(SCORES.USER_ID.eq(USERS.USER_ID))
|
.join(USERS).on(SCORES.USER_ID.eq(USERS.USER_ID))
|
||||||
@ -303,8 +273,7 @@ class ScoreService(
|
|||||||
beatmap_star_rating = it.get(BEATMAPS.STAR_RATING, Double::class.java),
|
beatmap_star_rating = it.get(BEATMAPS.STAR_RATING, Double::class.java),
|
||||||
pp = it.get(SCORES.PP, Double::class.java),
|
pp = it.get(SCORES.PP, Double::class.java),
|
||||||
frametime = it.get(SCORES.FRAMETIME, Double::class.java),
|
frametime = it.get(SCORES.FRAMETIME, Double::class.java),
|
||||||
ur = it.get(SCORES.UR, Double::class.java),
|
ur = it.get(SCORES.UR, Double::class.java)
|
||||||
leaderboard_rank = it.get(SCORES.LEADERBOARD_RANK, Long::class.java),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -349,17 +318,13 @@ class ScoreService(
|
|||||||
osuScoreAlias1.REPLAY_ID,
|
osuScoreAlias1.REPLAY_ID,
|
||||||
osuScoreAlias1.USER_ID,
|
osuScoreAlias1.USER_ID,
|
||||||
osuUserAlias1.USERNAME,
|
osuUserAlias1.USERNAME,
|
||||||
osuUserAlias1.IS_BANNED,
|
|
||||||
osuScoreAlias1.DATE,
|
osuScoreAlias1.DATE,
|
||||||
osuScoreAlias1.PP,
|
osuScoreAlias1.PP,
|
||||||
osuScoreAlias1.LEADERBOARD_RANK,
|
|
||||||
osuScoreAlias2.REPLAY_ID,
|
osuScoreAlias2.REPLAY_ID,
|
||||||
osuScoreAlias2.USER_ID,
|
osuScoreAlias2.USER_ID,
|
||||||
osuUserAlias2.USERNAME,
|
osuUserAlias2.USERNAME,
|
||||||
osuUserAlias2.IS_BANNED,
|
|
||||||
osuScoreAlias2.DATE,
|
osuScoreAlias2.DATE,
|
||||||
osuScoreAlias2.PP,
|
osuScoreAlias2.PP,
|
||||||
osuScoreAlias2.LEADERBOARD_RANK,
|
|
||||||
BEATMAPS.BEATMAP_ID,
|
BEATMAPS.BEATMAP_ID,
|
||||||
BEATMAPS.TITLE,
|
BEATMAPS.TITLE,
|
||||||
BEATMAPS.STAR_RATING,
|
BEATMAPS.STAR_RATING,
|
||||||
@ -379,10 +344,6 @@ class ScoreService(
|
|||||||
and(osuScoreAlias2.IS_BANNED.eq(false))
|
and(osuScoreAlias2.IS_BANNED.eq(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Globally skip maps that are known to have false positives (eg. high CS)
|
|
||||||
.and(SCORES_SIMILARITY.BEATMAP_ID.notIn(*SKIPPED_SIMILARITY_MAPS))
|
|
||||||
// Skip maps that have high CS values (smaller circles mean the replays will be naturally similar)
|
|
||||||
.and(BEATMAPS.CS.lt(8.0))
|
|
||||||
.and(condition)
|
.and(condition)
|
||||||
.orderBy(osuScoreAlias2.DATE.desc(), SCORES_SIMILARITY.SIMILARITY.asc())
|
.orderBy(osuScoreAlias2.DATE.desc(), SCORES_SIMILARITY.SIMILARITY.asc())
|
||||||
.fetch()
|
.fetch()
|
||||||
@ -402,8 +363,6 @@ class ScoreService(
|
|||||||
fun getSimilarReplays(condition: Condition = DSL.noCondition()): List<SimilarReplayEntry> {
|
fun getSimilarReplays(condition: Condition = DSL.noCondition()): List<SimilarReplayEntry> {
|
||||||
val replays = getSimilarReplaysRecords(condition)
|
val replays = getSimilarReplaysRecords(condition)
|
||||||
return mapSimilarReplays(replays)
|
return mapSimilarReplays(replays)
|
||||||
// Filter scores where the imports have been out of order and the stolen replay's user has been banned
|
|
||||||
.filter { !it.user_banned_2 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mapSimilarReplays(replays: List<Record>) = replays.map {
|
private fun mapSimilarReplays(replays: List<Record>) = replays.map {
|
||||||
@ -414,9 +373,6 @@ class ScoreService(
|
|||||||
var userId1 = it.get(osuScoreAlias1.USER_ID, Long::class.java)
|
var userId1 = it.get(osuScoreAlias1.USER_ID, Long::class.java)
|
||||||
var userId2 = it.get(osuScoreAlias2.USER_ID, Long::class.java)
|
var userId2 = it.get(osuScoreAlias2.USER_ID, Long::class.java)
|
||||||
|
|
||||||
var userBanned1 = it.get(osuUserAlias1.IS_BANNED, Boolean::class.java)
|
|
||||||
var userBanned2 = it.get(osuUserAlias2.IS_BANNED, Boolean::class.java)
|
|
||||||
|
|
||||||
var username1 = it.get(osuUserAlias1.USERNAME, String::class.java)
|
var username1 = it.get(osuUserAlias1.USERNAME, String::class.java)
|
||||||
var username2 = it.get(osuUserAlias2.USERNAME, String::class.java)
|
var username2 = it.get(osuUserAlias2.USERNAME, String::class.java)
|
||||||
|
|
||||||
@ -426,9 +382,6 @@ class ScoreService(
|
|||||||
var replayPp1 = it.get(osuScoreAlias1.PP, Double::class.java)
|
var replayPp1 = it.get(osuScoreAlias1.PP, Double::class.java)
|
||||||
var replayPp2 = it.get(osuScoreAlias2.PP, Double::class.java)
|
var replayPp2 = it.get(osuScoreAlias2.PP, Double::class.java)
|
||||||
|
|
||||||
var replayLeaderboardRank1 = it.get(osuScoreAlias1.LEADERBOARD_RANK, Long::class.java)
|
|
||||||
var replayLeaderboardRank2 = it.get(osuScoreAlias2.LEADERBOARD_RANK, Long::class.java)
|
|
||||||
|
|
||||||
// Swap logic if replayDate1 is after replayDate2
|
// Swap logic if replayDate1 is after replayDate2
|
||||||
if (replayDate1.isAfter(replayDate2)) {
|
if (replayDate1.isAfter(replayDate2)) {
|
||||||
val tempReplayId = replayId1
|
val tempReplayId = replayId1
|
||||||
@ -439,10 +392,6 @@ class ScoreService(
|
|||||||
userId1 = userId2
|
userId1 = userId2
|
||||||
userId2 = tempUserId
|
userId2 = tempUserId
|
||||||
|
|
||||||
val tempUserBanned = userBanned1
|
|
||||||
userBanned1 = userBanned2
|
|
||||||
userBanned2 = tempUserBanned
|
|
||||||
|
|
||||||
val tempUsername = username1
|
val tempUsername = username1
|
||||||
username1 = username2
|
username1 = username2
|
||||||
username2 = tempUsername
|
username2 = tempUsername
|
||||||
@ -454,10 +403,6 @@ class ScoreService(
|
|||||||
val tempReplayPp = replayPp1
|
val tempReplayPp = replayPp1
|
||||||
replayPp1 = replayPp2
|
replayPp1 = replayPp2
|
||||||
replayPp2 = tempReplayPp
|
replayPp2 = tempReplayPp
|
||||||
|
|
||||||
val tempLeaderboardRank = replayLeaderboardRank1
|
|
||||||
replayLeaderboardRank1 = replayLeaderboardRank2
|
|
||||||
replayLeaderboardRank2 = tempLeaderboardRank
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SimilarReplayEntry(
|
SimilarReplayEntry(
|
||||||
@ -465,8 +410,6 @@ class ScoreService(
|
|||||||
replay_id_2 = replayId2,
|
replay_id_2 = replayId2,
|
||||||
user_id_1 = userId1,
|
user_id_1 = userId1,
|
||||||
user_id_2 = userId2,
|
user_id_2 = userId2,
|
||||||
user_banned_1 = userBanned1,
|
|
||||||
user_banned_2 = userBanned2,
|
|
||||||
username_1 = username1,
|
username_1 = username1,
|
||||||
username_2 = username2,
|
username_2 = username2,
|
||||||
beatmap_beatmapset_id = it.get(BEATMAPS.BEATMAPSET_ID, Long::class.java),
|
beatmap_beatmapset_id = it.get(BEATMAPS.BEATMAPSET_ID, Long::class.java),
|
||||||
@ -474,12 +417,10 @@ class ScoreService(
|
|||||||
replay_date_2 = Format.formatLocalDateTime(replayDate2),
|
replay_date_2 = Format.formatLocalDateTime(replayDate2),
|
||||||
replay_pp_1 = replayPp1,
|
replay_pp_1 = replayPp1,
|
||||||
replay_pp_2 = replayPp2,
|
replay_pp_2 = replayPp2,
|
||||||
replay_leaderboard_rank_1 = replayLeaderboardRank1,
|
|
||||||
replay_leaderboard_rank_2 = replayLeaderboardRank2,
|
|
||||||
beatmap_id = it.get(BEATMAPS.BEATMAP_ID, Long::class.java),
|
beatmap_id = it.get(BEATMAPS.BEATMAP_ID, Long::class.java),
|
||||||
beatmap_title = it.get(BEATMAPS.TITLE, String::class.java),
|
beatmap_title = it.get(BEATMAPS.TITLE, String::class.java),
|
||||||
beatmap_star_rating = it.get(BEATMAPS.STAR_RATING, Double::class.java),
|
beatmap_star_rating = it.get(BEATMAPS.STAR_RATING, Double::class.java),
|
||||||
similarity = it.get(SCORES_SIMILARITY.SIMILARITY, Double::class.java),
|
similarity = it.get(SCORES_SIMILARITY.SIMILARITY, Double::class.java)
|
||||||
)
|
)
|
||||||
}.distinctBy {
|
}.distinctBy {
|
||||||
val (smallerId, largerId) = listOf(it.replay_id_1, it.replay_id_2).sorted()
|
val (smallerId, largerId) = listOf(it.replay_id_1, it.replay_id_2).sorted()
|
||||||
|
|||||||
@ -65,7 +65,7 @@ class UserScoreService(
|
|||||||
USER_SCORES.ERROR_SKEWNESS,
|
USER_SCORES.ERROR_SKEWNESS,
|
||||||
USER_SCORES.SLIDEREND_RELEASE_TIMES,
|
USER_SCORES.SLIDEREND_RELEASE_TIMES,
|
||||||
USER_SCORES.KEYPRESSES_TIMES,
|
USER_SCORES.KEYPRESSES_TIMES,
|
||||||
USER_SCORES.JUDGEMENTS,
|
USER_SCORES.JUDGEMENTS
|
||||||
)
|
)
|
||||||
.from(USER_SCORES)
|
.from(USER_SCORES)
|
||||||
.join(BEATMAPS).on(USER_SCORES.BEATMAP_ID.eq(BEATMAPS.BEATMAP_ID))
|
.join(BEATMAPS).on(USER_SCORES.BEATMAP_ID.eq(BEATMAPS.BEATMAP_ID))
|
||||||
@ -127,8 +127,7 @@ class UserScoreService(
|
|||||||
date = null,
|
date = null,
|
||||||
pp = null,
|
pp = null,
|
||||||
rank = null,
|
rank = null,
|
||||||
user_id = null,
|
user_id = null
|
||||||
leaderboard_rank = null,
|
|
||||||
)
|
)
|
||||||
this.scoreService.loadComparableReplayData(replayData)
|
this.scoreService.loadComparableReplayData(replayData)
|
||||||
return replayData
|
return replayData
|
||||||
|
|||||||
@ -61,8 +61,7 @@ class CircleguardService {
|
|||||||
val sliderend_release_standard_deviation: Double?,
|
val sliderend_release_standard_deviation: Double?,
|
||||||
val sliderend_release_standard_deviation_adjusted: Double?,
|
val sliderend_release_standard_deviation_adjusted: Double?,
|
||||||
|
|
||||||
val judgements: List<Judgement>,
|
val judgements: List<Judgement>
|
||||||
val hit_count: Int?,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun postProcessReplay(replayResponse: ReplayResponse, mods: Int = 0) {
|
fun postProcessReplay(replayResponse: ReplayResponse, mods: Int = 0) {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import java.util.*
|
|||||||
@Service
|
@Service
|
||||||
class MetabaseService : InitializingBean {
|
class MetabaseService : InitializingBean {
|
||||||
|
|
||||||
@Value("\${METABASE_API_KEY:nil}")
|
@Value("\${METABASE_API_KEY}")
|
||||||
private lateinit var metabaseApiKey: String
|
private lateinit var metabaseApiKey: String
|
||||||
|
|
||||||
@Value("\${METABASE_URL:https://neko.nise.moe}")
|
@Value("\${METABASE_URL:https://neko.nise.moe}")
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory
|
|||||||
import org.springframework.beans.factory.InitializingBean
|
import org.springframework.beans.factory.InitializingBean
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.io.IOException
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.net.http.HttpClient
|
import java.net.http.HttpClient
|
||||||
@ -242,22 +241,6 @@ class OsuApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserBeatmapScore(userId: Long, beatmapId: Int, scoreId: Long? = null): OsuApiModels.UserScore? {
|
|
||||||
val response = doRequest("https://osu.ppy.sh/api/v2/beatmaps/$beatmapId/scores/users/$userId", emptyMap())
|
|
||||||
|
|
||||||
if (response == null) {
|
|
||||||
this.logger.info("Error getting score on beatmap $beatmapId for user $userId")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val score = when (response.statusCode()) {
|
|
||||||
200 -> serializer.decodeFromString<OsuApiModels.UserScore>(response.body())
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (scoreId == null || score?.score?.id == scoreId) score else null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun searchBeatmapsets(cursor: OsuApiModels.BeatmapsetSearchResultCursor?): OsuApiModels.BeatmapsetSearchResult? {
|
fun searchBeatmapsets(cursor: OsuApiModels.BeatmapsetSearchResultCursor?): OsuApiModels.BeatmapsetSearchResult? {
|
||||||
val queryParams = mutableMapOf(
|
val queryParams = mutableMapOf(
|
||||||
"s" to "ranked", // Status [only ranked]
|
"s" to "ranked", // Status [only ranked]
|
||||||
@ -363,12 +346,7 @@ class OsuApi(
|
|||||||
val waitTimes = listOf(15L, 30L, 60L)
|
val waitTimes = listOf(15L, 30L, 60L)
|
||||||
|
|
||||||
for (waitTime in waitTimes) {
|
for (waitTime in waitTimes) {
|
||||||
val response = try {
|
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
|
||||||
httpClient.send(request, HttpResponse.BodyHandlers.ofString())
|
|
||||||
} catch (ex: IOException) {
|
|
||||||
// Some transport level exception might be thrown, continue with the retry backoff and see if it fixes itself
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.debug("Request: {}", request.uri())
|
this.logger.debug("Request: {}", request.uri())
|
||||||
this.logger.debug("Result: {}", response.statusCode())
|
this.logger.debug("Result: {}", response.statusCode())
|
||||||
|
|||||||
@ -140,12 +140,6 @@ class OsuApiModels {
|
|||||||
val scores: List<Score>
|
val scores: List<Score>
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class UserScore(
|
|
||||||
val score: Score,
|
|
||||||
val position: Long,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class Grade {
|
enum class Grade {
|
||||||
@SerialName("XH")
|
@SerialName("XH")
|
||||||
|
|||||||
@ -1,283 +0,0 @@
|
|||||||
package com.nisemoe.nise.replays
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.round
|
|
||||||
|
|
||||||
// JVM implementation of https://github.com/circleguard/wtc-lzma-compressor/tree/master
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WTC {
|
|
||||||
companion object {
|
|
||||||
private const val CURRENT_VERSION_HEADER: Short = 1
|
|
||||||
|
|
||||||
private val VERSION_HEADER_BYTE_ARRAY = byteArrayOf((CURRENT_VERSION_HEADER.toInt() and 0xFF).toByte(), ((CURRENT_VERSION_HEADER.toInt() shr 8) and 0xFF).toByte())
|
|
||||||
|
|
||||||
fun compress(stream: String): ByteArray {
|
|
||||||
val lists = separate(stream)
|
|
||||||
|
|
||||||
val xs = unsortedDiffPackShortsToBytes(lists.x)
|
|
||||||
val ys = unsortedDiffPackShortsToBytes(lists.y)
|
|
||||||
|
|
||||||
val ws = packIntsToBytes(lists.w)
|
|
||||||
val zs = lists.z
|
|
||||||
|
|
||||||
fun packBytes(arr: ByteArray): ByteArray {
|
|
||||||
val length = arr.size
|
|
||||||
val buffer = ByteBuffer.allocate(4 + length).order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
|
|
||||||
buffer.putInt(length)
|
|
||||||
for (byte in arr) {
|
|
||||||
buffer.put(byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.array()
|
|
||||||
}
|
|
||||||
|
|
||||||
val byteStream = ByteArrayOutputStream()
|
|
||||||
|
|
||||||
byteStream.writeBytes(VERSION_HEADER_BYTE_ARRAY)
|
|
||||||
byteStream.writeBytes(packBytes(xs))
|
|
||||||
byteStream.writeBytes(packBytes(ys))
|
|
||||||
byteStream.writeBytes(packBytes(zs))
|
|
||||||
byteStream.writeBytes(packBytes(ws))
|
|
||||||
|
|
||||||
return byteStream.toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun decompress(data: ByteArray, hasVersionHeader: Boolean = true): String {
|
|
||||||
val buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
|
|
||||||
fun unpackBytes(): ByteArray {
|
|
||||||
val size = buffer.getInt()
|
|
||||||
|
|
||||||
val bytes = ByteArray(size)
|
|
||||||
buffer.get(bytes, 0, size)
|
|
||||||
|
|
||||||
return bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasVersionHeader) {
|
|
||||||
buffer.getShort() // Version - may be used in the future
|
|
||||||
}
|
|
||||||
|
|
||||||
val xs = unpackBytes()
|
|
||||||
val ys = unpackBytes()
|
|
||||||
val zs = unpackBytes()
|
|
||||||
val ws = unpackBytes()
|
|
||||||
|
|
||||||
val xxs = unsortedDiffUnpackBytesToShorts(xs)
|
|
||||||
val yys = unsortedDiffUnpackBytesToShorts(ys)
|
|
||||||
|
|
||||||
val wws = unpackBytesToInts(ws)
|
|
||||||
|
|
||||||
return combine(FrameLists(
|
|
||||||
x = xxs,
|
|
||||||
y = yys,
|
|
||||||
z = zs,
|
|
||||||
w = wws,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
data class FrameLists(
|
|
||||||
val x: ShortArray,
|
|
||||||
val y: ShortArray,
|
|
||||||
val z: ByteArray,
|
|
||||||
val w: IntArray,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun unsortedDiffPackShortsToBytes(shorts: ShortArray): ByteArray {
|
|
||||||
val start = shorts.first()
|
|
||||||
val diff = arrayDiff(shorts)
|
|
||||||
val packed = mutableListOf<Byte>()
|
|
||||||
|
|
||||||
fun pack(word: Short) {
|
|
||||||
if (abs(word.toInt()) <= Byte.MAX_VALUE) {
|
|
||||||
packed.add(word.toByte())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
packed.add(Byte.MIN_VALUE)
|
|
||||||
packed.add((word.toInt() and 0xFF).toByte())
|
|
||||||
packed.add((word.toInt() shr 8).toByte())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pack(start)
|
|
||||||
for (word in diff) {
|
|
||||||
pack(word)
|
|
||||||
}
|
|
||||||
|
|
||||||
return packed.toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unsortedDiffUnpackBytesToShorts(int8s: ByteArray): ShortArray {
|
|
||||||
val decoded = mutableListOf<Short>()
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
while (i < int8s.size) {
|
|
||||||
val byte = int8s[i]
|
|
||||||
|
|
||||||
if (byte == Byte.MIN_VALUE) {
|
|
||||||
i++
|
|
||||||
var word = int8s[i].toInt() and 0xFF
|
|
||||||
i++
|
|
||||||
word += int8s[i].toInt() shl 8
|
|
||||||
decoded.add(word.toShort())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
decoded.add(byte.toShort())
|
|
||||||
}
|
|
||||||
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return cumSum(decoded.toShortArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun packIntsToBytes(int32s: IntArray): ByteArray {
|
|
||||||
val packed = mutableListOf<Byte>()
|
|
||||||
|
|
||||||
for (dw in int32s) {
|
|
||||||
var dword = dw
|
|
||||||
if (abs(dword) <= Byte.MAX_VALUE) {
|
|
||||||
packed.add(dword.toByte())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
packed.add(Byte.MIN_VALUE)
|
|
||||||
packed.add((dword and 0xFF).toByte())
|
|
||||||
dword = dword shr 8
|
|
||||||
packed.add((dword and 0xFF).toByte())
|
|
||||||
dword = dword shr 8
|
|
||||||
packed.add((dword and 0xFF).toByte())
|
|
||||||
dword = dword shr 8
|
|
||||||
packed.add(dword.toByte())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return packed.toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unpackBytesToInts(int8s: ByteArray): IntArray {
|
|
||||||
val unpacked = mutableListOf<Int>()
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
while (i < int8s.size) {
|
|
||||||
val byte = int8s[i]
|
|
||||||
|
|
||||||
if (byte == Byte.MIN_VALUE) {
|
|
||||||
i++
|
|
||||||
var dword = int8s[i].toInt() and 0xFF
|
|
||||||
i++
|
|
||||||
dword += (int8s[i].toInt() shl 8) and 0xFF00
|
|
||||||
i++
|
|
||||||
dword += (int8s[i].toInt() shl 16) and 0xFF0000
|
|
||||||
i++
|
|
||||||
dword += int8s[i].toInt() shl 24
|
|
||||||
unpacked.add(dword)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
unpacked.add(byte.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return unpacked.toIntArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun separate(stream: String): FrameLists {
|
|
||||||
val frames = stream.split(',')
|
|
||||||
val frameCount = frames.size
|
|
||||||
|
|
||||||
val ws = IntArray(frameCount)
|
|
||||||
val xs = ShortArray(frameCount)
|
|
||||||
val ys = ShortArray(frameCount)
|
|
||||||
val zs = ByteArray(frameCount)
|
|
||||||
|
|
||||||
for ((i, frame) in frames.withIndex()) {
|
|
||||||
if (frame.isEmpty()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val splitFrame = frame.split('|')
|
|
||||||
val w = splitFrame[0].toInt()
|
|
||||||
val x = splitFrame[1].toFloat()
|
|
||||||
val y = splitFrame[2].toFloat()
|
|
||||||
val z = splitFrame[3].toInt()
|
|
||||||
|
|
||||||
val zz = z and 0xFF
|
|
||||||
|
|
||||||
var xx = round(x * 16).toInt()
|
|
||||||
var yy = round(y * 16).toInt()
|
|
||||||
|
|
||||||
if (xx <= -0x8000) xx = -0x8000
|
|
||||||
else if (xx >= 0x7FFF) xx = 0x7FFF
|
|
||||||
|
|
||||||
if (yy <= -0x8000) yy = -0x8000
|
|
||||||
else if (yy >= 0x7FFF) yy = 0x7FFF
|
|
||||||
|
|
||||||
ws[i] = w
|
|
||||||
xs[i] = xx.toShort()
|
|
||||||
ys[i] = yy.toShort()
|
|
||||||
zs[i] = zz.toByte()
|
|
||||||
}
|
|
||||||
|
|
||||||
return FrameLists(
|
|
||||||
x = xs,
|
|
||||||
y = ys,
|
|
||||||
z = zs,
|
|
||||||
w = ws,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun combine(lists: FrameLists): String {
|
|
||||||
val xArr = lists.x.map { it.toFloat() / 16 }
|
|
||||||
|
|
||||||
val yArr = lists.y.map { it.toFloat() / 16 }
|
|
||||||
|
|
||||||
val frames = arrayOfNulls<String>(xArr.size)
|
|
||||||
|
|
||||||
for (i in xArr.indices) {
|
|
||||||
val x = xArr[i]
|
|
||||||
val y = yArr[i]
|
|
||||||
val z = lists.z[i]
|
|
||||||
val w = lists.w[i]
|
|
||||||
frames[i] = "$w|$x|$y|$z"
|
|
||||||
}
|
|
||||||
|
|
||||||
return frames.joinToString(",")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun arrayDiff(arr: ShortArray): ShortArray {
|
|
||||||
if (arr.isEmpty()) {
|
|
||||||
return emptyArray<Short>().toShortArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
val diffed = ShortArray(arr.size - 1)
|
|
||||||
|
|
||||||
for (index in 1..<arr.size) {
|
|
||||||
diffed[index - 1] = (arr[index] - arr[index - 1]).toShort()
|
|
||||||
}
|
|
||||||
|
|
||||||
return diffed
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cumSum(arr: ShortArray): ShortArray {
|
|
||||||
if (arr.isEmpty()) {
|
|
||||||
return emptyArray<Short>().toShortArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
val cumArr = ShortArray(arr.size)
|
|
||||||
|
|
||||||
cumArr[0] = arr.first()
|
|
||||||
for (index in 1..<arr.size) {
|
|
||||||
cumArr[index] = (arr[index] + cumArr[index - 1]).toShort()
|
|
||||||
}
|
|
||||||
|
|
||||||
return cumArr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -28,9 +28,9 @@ class RssService(
|
|||||||
items.subList(0, items.size.coerceAtMost(50))
|
items.subList(0, items.size.coerceAtMost(50))
|
||||||
|
|
||||||
val channel = Channel(
|
val channel = Channel(
|
||||||
title = "nise.stedos.dev's feed and sneed",
|
title = "nise.moe's feed and sneed",
|
||||||
link = "https://nise.stedos.dev/rss",
|
link = "https://nise.moe/rss",
|
||||||
description = "Feed of *sus* scores for osu!std - /nise.stedos.dev/",
|
description = "Feed of *sus* scores for osu!std - /nise.moe/",
|
||||||
lastBuildDate = Date().toInstant().atZone(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME),
|
lastBuildDate = Date().toInstant().atZone(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME),
|
||||||
item = items.map {
|
item = items.map {
|
||||||
Item(
|
Item(
|
||||||
@ -42,7 +42,7 @@ class RssService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
},
|
},
|
||||||
atomLink = AtomLink(href = "https://nise.stedos.dev/rss.xml")
|
atomLink = AtomLink(href = "https://nise.moe/rss.xml")
|
||||||
)
|
)
|
||||||
|
|
||||||
return RssFeed(channel = channel)
|
return RssFeed(channel = channel)
|
||||||
@ -74,8 +74,8 @@ class RssService(
|
|||||||
if(score1.addedAt != null) {
|
if(score1.addedAt != null) {
|
||||||
val item = RssFeedController.IntermeriaryFeedItem(
|
val item = RssFeedController.IntermeriaryFeedItem(
|
||||||
title = "Possible stolen replay",
|
title = "Possible stolen replay",
|
||||||
guid = "https://nise.stedos.dev/p/${score1.replayId}/${score2.replayId}",
|
guid = "https://nise.moe/p/${score1.replayId}/${score2.replayId}",
|
||||||
link = "https://nise.stedos.dev/p/${score1.replayId}/${score2.replayId}",
|
link = "https://nise.moe/p/${score1.replayId}/${score2.replayId}",
|
||||||
description = "Similarity: ${result[SCORES_SIMILARITY.SIMILARITY]}%\n" +
|
description = "Similarity: ${result[SCORES_SIMILARITY.SIMILARITY]}%\n" +
|
||||||
"Replay by ${user1.username} on ${beatmap.artist} - ${beatmap.title} [${beatmap.version}] (${beatmap.starRating} stars)\n" +
|
"Replay by ${user1.username} on ${beatmap.artist} - ${beatmap.title} [${beatmap.version}] (${beatmap.starRating} stars)\n" +
|
||||||
"Replay by ${user2.username} on ${beatmap.artist} - ${beatmap.title} [${beatmap.version}] (${beatmap.starRating} stars)",
|
"Replay by ${user2.username} on ${beatmap.artist} - ${beatmap.title} [${beatmap.version}] (${beatmap.starRating} stars)",
|
||||||
@ -113,8 +113,8 @@ class RssService(
|
|||||||
if(score.addedAt != null) {
|
if(score.addedAt != null) {
|
||||||
val item = RssFeedController.IntermeriaryFeedItem(
|
val item = RssFeedController.IntermeriaryFeedItem(
|
||||||
title = "Suspicious score by ${user.username}",
|
title = "Suspicious score by ${user.username}",
|
||||||
guid = "https://nise.stedos.dev/s/${score.replayId}",
|
guid = "https://nise.moe/s/${score.replayId}",
|
||||||
link = "https://nise.stedos.dev/s/${score.replayId}",
|
link = "https://nise.moe/s/${score.replayId}",
|
||||||
description = "New score by ${user.username} on ${beatmap.artist} - ${beatmap.title} [${beatmap.version}] (${beatmap.starRating} stars)",
|
description = "New score by ${user.username} on ${beatmap.artist} - ${beatmap.title} [${beatmap.version}] (${beatmap.starRating} stars)",
|
||||||
pubDate = score.addedAt!!
|
pubDate = score.addedAt!!
|
||||||
)
|
)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory
|
|||||||
import org.springframework.scheduling.annotation.Scheduled
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.util.StopWatch
|
import org.springframework.util.StopWatch
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class GlobalCache(
|
class GlobalCache(
|
||||||
@ -33,7 +34,7 @@ class GlobalCache(
|
|||||||
fun updateCaches() {
|
fun updateCaches() {
|
||||||
val stopwatch = StopWatch()
|
val stopwatch = StopWatch()
|
||||||
stopwatch.start()
|
stopwatch.start()
|
||||||
logger.info("Updating the cache...")
|
logger.info("Updating the cache!")
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val rssFeedDeferred = async { rssService.generateFeed() }
|
val rssFeedDeferred = async { rssService.generateFeed() }
|
||||||
@ -49,8 +50,6 @@ class GlobalCache(
|
|||||||
|
|
||||||
stopwatch.stop()
|
stopwatch.stop()
|
||||||
logger.info("Cache updated in {} seconds", String.format("%.2f", stopwatch.totalTimeSeconds))
|
logger.info("Cache updated in {} seconds", String.format("%.2f", stopwatch.totalTimeSeconds))
|
||||||
logger.info("[CACHE]: Similar replays count: {}", similarReplays?.size)
|
|
||||||
logger.info("[CACHE]: Suspicious scores count: {}", suspiciousScores?.size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -293,7 +293,7 @@ class ImportScores(
|
|||||||
dslContext.update(BEATMAPS)
|
dslContext.update(BEATMAPS)
|
||||||
.set(BEATMAPS.BEATMAP_HASH, topScore.beatmap.checksum)
|
.set(BEATMAPS.BEATMAP_HASH, topScore.beatmap.checksum)
|
||||||
.set(BEATMAPS.STAR_RATING, topScore.beatmap.difficulty_rating)
|
.set(BEATMAPS.STAR_RATING, topScore.beatmap.difficulty_rating)
|
||||||
.set(BEATMAPS.VERSION, topScore.beatmap.version)
|
.set(BEATMAPS.VERSION, beatmap.version)
|
||||||
.set(BEATMAPS.ARTIST, topScore.beatmapset!!.artist)
|
.set(BEATMAPS.ARTIST, topScore.beatmapset!!.artist)
|
||||||
.set(BEATMAPS.SOURCE, topScore.beatmapset.source)
|
.set(BEATMAPS.SOURCE, topScore.beatmapset.source)
|
||||||
.set(BEATMAPS.TITLE, topScore.beatmapset.title)
|
.set(BEATMAPS.TITLE, topScore.beatmapset.title)
|
||||||
@ -727,8 +727,6 @@ class ImportScores(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val topScore = osuApi.getUserBeatmapScore(score.user_id, beatmapId, score.best_id)
|
|
||||||
|
|
||||||
dslContext.insertInto(SCORES)
|
dslContext.insertInto(SCORES)
|
||||||
.set(SCORES.BEATMAP_ID, beatmapId)
|
.set(SCORES.BEATMAP_ID, beatmapId)
|
||||||
.set(SCORES.COUNT_300, score.statistics.count_300)
|
.set(SCORES.COUNT_300, score.statistics.count_300)
|
||||||
@ -747,7 +745,6 @@ class ImportScores(
|
|||||||
.set(SCORES.REPLAY_ID, score.best_id)
|
.set(SCORES.REPLAY_ID, score.best_id)
|
||||||
.set(SCORES.USER_ID, score.user_id)
|
.set(SCORES.USER_ID, score.user_id)
|
||||||
.set(SCORES.VERSION, CURRENT_VERSION)
|
.set(SCORES.VERSION, CURRENT_VERSION)
|
||||||
.set(SCORES.LEADERBOARD_RANK, topScore?.position)
|
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
this.statistics.scoresAddedToDatabase++
|
this.statistics.scoresAddedToDatabase++
|
||||||
@ -801,12 +798,6 @@ class ImportScores(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the score has a low amount of hits the UR calculation will be inaccurate, skip these plays
|
|
||||||
if (processedReplay.hit_count == null || processedReplay.hit_count < 10) {
|
|
||||||
this.logger.warn("Processed play has less than 10 hits, skipping score ${score.id}")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val compressedReplay = CompressReplay.compressReplay(scoreReplay.content.toByteArray())
|
val compressedReplay = CompressReplay.compressReplay(scoreReplay.content.toByteArray())
|
||||||
|
|
||||||
val scoreId = dslContext.update(SCORES)
|
val scoreId = dslContext.update(SCORES)
|
||||||
|
|||||||
@ -78,7 +78,6 @@ class ScoreSearchController(
|
|||||||
val perfect: Boolean?,
|
val perfect: Boolean?,
|
||||||
val pp: Double?,
|
val pp: Double?,
|
||||||
val rank: String?,
|
val rank: String?,
|
||||||
val leaderboard_rank: Long?,
|
|
||||||
val replay_id: Long?,
|
val replay_id: Long?,
|
||||||
val score: Long?,
|
val score: Long?,
|
||||||
val ur: Double?,
|
val ur: Double?,
|
||||||
|
|||||||
@ -51,7 +51,6 @@ class ScoreSearchSchemaController(
|
|||||||
InternalSchemaField("perfect", "Perfect", Category.score, Type.boolean, false, "if score is a full combo", databaseField = SCORES.PERFECT),
|
InternalSchemaField("perfect", "Perfect", Category.score, Type.boolean, false, "if score is a full combo", databaseField = SCORES.PERFECT),
|
||||||
InternalSchemaField("pp", "Score PP", Category.score, Type.number, true, "performance points for score", databaseField = SCORES.PP),
|
InternalSchemaField("pp", "Score PP", Category.score, Type.number, true, "performance points for score", databaseField = SCORES.PP),
|
||||||
InternalSchemaField("rank", "Rank", Category.score, Type.grade, false, "score grade", databaseField = SCORES.RANK),
|
InternalSchemaField("rank", "Rank", Category.score, Type.grade, false, "score grade", databaseField = SCORES.RANK),
|
||||||
InternalSchemaField("leaderboard_rank", "Leaderboard Rank", Category.score, Type.number, false, "leaderboard position of the play at import", databaseField = SCORES.LEADERBOARD_RANK),
|
|
||||||
InternalSchemaField("replay_id", "Replay ID", Category.score, Type.number, false, "identifier for replay", databaseField = SCORES.REPLAY_ID),
|
InternalSchemaField("replay_id", "Replay ID", Category.score, Type.number, false, "identifier for replay", databaseField = SCORES.REPLAY_ID),
|
||||||
InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value", databaseField = SCORES.SCORE),
|
InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value", databaseField = SCORES.SCORE),
|
||||||
InternalSchemaField("ur", "UR", Category.metrics, Type.number, false, "unstable rate", databaseField = SCORES.UR),
|
InternalSchemaField("ur", "UR", Category.metrics, Type.number, false, "unstable rate", databaseField = SCORES.UR),
|
||||||
|
|||||||
@ -83,7 +83,6 @@ class ScoreSearchService(
|
|||||||
SCORES.ERROR_COEFFICIENT_OF_VARIATION,
|
SCORES.ERROR_COEFFICIENT_OF_VARIATION,
|
||||||
SCORES.ERROR_KURTOSIS,
|
SCORES.ERROR_KURTOSIS,
|
||||||
SCORES.ERROR_SKEWNESS,
|
SCORES.ERROR_SKEWNESS,
|
||||||
SCORES.LEADERBOARD_RANK,
|
|
||||||
|
|
||||||
// Beatmaps fields
|
// Beatmaps fields
|
||||||
BEATMAPS.ARTIST,
|
BEATMAPS.ARTIST,
|
||||||
@ -187,7 +186,6 @@ class ScoreSearchService(
|
|||||||
perfect = it.get(SCORES.PERFECT),
|
perfect = it.get(SCORES.PERFECT),
|
||||||
pp = it.get(SCORES.PP)?.roundToInt()?.toDouble(),
|
pp = it.get(SCORES.PP)?.roundToInt()?.toDouble(),
|
||||||
rank = it.get(SCORES.RANK),
|
rank = it.get(SCORES.RANK),
|
||||||
leaderboard_rank = it.get(SCORES.LEADERBOARD_RANK),
|
|
||||||
replay_id = it.get(SCORES.REPLAY_ID),
|
replay_id = it.get(SCORES.REPLAY_ID),
|
||||||
score = it.get(SCORES.SCORE),
|
score = it.get(SCORES.SCORE),
|
||||||
ur = it.get(SCORES.UR),
|
ur = it.get(SCORES.UR),
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
ALTER TABLE public.scores ADD COLUMN leaderboard_rank BIGINT;
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
package com.nisemoe.nise.osu
|
|
||||||
|
|
||||||
import com.nisemoe.nise.replays.WTC
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest
|
|
||||||
import org.junit.jupiter.params.provider.ValueSource
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.testcontainers.shaded.org.bouncycastle.util.Arrays
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class WtcTest {
|
|
||||||
private val resourcesPath = Paths.get("src", "test", "resources", "wtc")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares the bytes produced from the `wtcCompress()` function with bytes produced from the Python implementation of WTC.
|
|
||||||
*/
|
|
||||||
@ParameterizedTest
|
|
||||||
@ValueSource(strings = ["replay_1", "replay_2", "replay_3", "replay_4"])
|
|
||||||
fun wtcCompressReturnsCorrectBytes(replayName: String) {
|
|
||||||
val expected = resourcesPath.resolve("${replayName}_compressed.dat").toFile().readBytes()
|
|
||||||
val replayEvents = resourcesPath.resolve("${replayName}_events.txt").toFile().readText()
|
|
||||||
|
|
||||||
val wtcCompressed = WTC.compress(replayEvents)
|
|
||||||
|
|
||||||
// We include a version header at the start of the compressed byte array - create a new array excluding these
|
|
||||||
// so we can compare just the raw data with the Python WTC implementation's output.
|
|
||||||
// (remove the first two bytes since the version header is a Short)
|
|
||||||
val wtcCompressedWithoutVersionHeader = Arrays.copyOfRange(wtcCompressed, 2, wtcCompressed.size)
|
|
||||||
|
|
||||||
Assertions.assertArrayEquals(expected, wtcCompressedWithoutVersionHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@ValueSource(strings = ["replay_1", "replay_2", "replay_3", "replay_4"])
|
|
||||||
fun wtcDecompressReturnsCorrectReplayFrames(replayName: String) {
|
|
||||||
val expected = resourcesPath.resolve("${replayName}_decompressed.txt").toFile().readText()
|
|
||||||
val compressedReplay = resourcesPath.resolve("${replayName}_compressed.dat").toFile().readBytes()
|
|
||||||
|
|
||||||
val wtcDecompressed = WTC.decompress(compressedReplay, false)
|
|
||||||
|
|
||||||
assertEquals(expected, wtcDecompressed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -102,7 +102,6 @@ class ReplayResponse:
|
|||||||
sliderend_release_standard_deviation_adjusted: float
|
sliderend_release_standard_deviation_adjusted: float
|
||||||
|
|
||||||
judgements: List[Hit]
|
judgements: List[Hit]
|
||||||
hit_count: int
|
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
d = asdict(self)
|
d = asdict(self)
|
||||||
@ -218,8 +217,7 @@ async def process_replay(request: Request):
|
|||||||
sliderend_release_standard_deviation=np.std(se, ddof=1),
|
sliderend_release_standard_deviation=np.std(se, ddof=1),
|
||||||
sliderend_release_standard_deviation_adjusted=np.std(my_filter_outliers(se), ddof=1),
|
sliderend_release_standard_deviation_adjusted=np.std(my_filter_outliers(se), ddof=1),
|
||||||
|
|
||||||
judgements=judgements,
|
judgements=judgements
|
||||||
hit_count=len(hits)
|
|
||||||
)
|
)
|
||||||
return json(ur_response.to_dict())
|
return json(ur_response.to_dict())
|
||||||
|
|
||||||
|
|||||||
@ -34,5 +34,5 @@
|
|||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
<div class="text-center version">
|
<div class="text-center version">
|
||||||
v20250622
|
v20250213
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -97,10 +97,9 @@ export interface ReplayData {
|
|||||||
snaps: number;
|
snaps: number;
|
||||||
hits: number;
|
hits: number;
|
||||||
|
|
||||||
pp: number;
|
pp: number,
|
||||||
perfect: boolean;
|
perfect: boolean;
|
||||||
max_combo: number;
|
max_combo: number,
|
||||||
leaderboard_rank?: number;
|
|
||||||
|
|
||||||
mean_error?: number,
|
mean_error?: number,
|
||||||
error_variance?: number,
|
error_variance?: number,
|
||||||
@ -157,7 +156,6 @@ export interface SuspiciousScore {
|
|||||||
pp: number;
|
pp: number;
|
||||||
frametime: number;
|
frametime: number;
|
||||||
ur: number;
|
ur: number;
|
||||||
leaderboard_rank: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SimilarReplay {
|
export interface SimilarReplay {
|
||||||
@ -170,8 +168,6 @@ export interface SimilarReplay {
|
|||||||
replay_date_2: string;
|
replay_date_2: string;
|
||||||
replay_pp_1: number;
|
replay_pp_1: number;
|
||||||
replay_pp_2: number;
|
replay_pp_2: number;
|
||||||
replay_leaderboard_rank_1: number;
|
|
||||||
replay_leaderboard_rank_2: number;
|
|
||||||
beatmap_id: number;
|
beatmap_id: number;
|
||||||
beatmap_title: string;
|
beatmap_title: string;
|
||||||
beatmap_star_rating: number;
|
beatmap_star_rating: number;
|
||||||
|
|||||||
@ -129,7 +129,7 @@
|
|||||||
<td *ngFor="let column of fields" [hidden]="!column.active" class="text-center" style="line-height: 32px">
|
<td *ngFor="let column of fields" [hidden]="!column.active" class="text-center" style="line-height: 32px">
|
||||||
<ng-container *ngIf="getValue(entry, column.name) !== null; else nullDisplay">
|
<ng-container *ngIf="getValue(entry, column.name) !== null; else nullDisplay">
|
||||||
<ng-container *ngIf="column.type == 'number'">
|
<ng-container *ngIf="column.type == 'number'">
|
||||||
{{ isFieldId(column.name) ? getValue(entry, column.name) : (getValue(entry, column.name) | number) }}
|
{{ getValue(entry, column.name) | number }}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="column.type == 'flag'">
|
<ng-container *ngIf="column.type == 'flag'">
|
||||||
<span class="flag" [title]="getValue(entry, column.name)">{{ countryCodeToFlag(getValue(entry, column.name)) }}</span>
|
<span class="flag" [title]="getValue(entry, column.name)">{{ countryCodeToFlag(getValue(entry, column.name)) }}</span>
|
||||||
|
|||||||
@ -352,8 +352,6 @@ export class SearchComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isFieldId = (field: string): boolean => field === 'id' || field.includes("_id");
|
|
||||||
|
|
||||||
protected readonly countryCodeToFlag = countryCodeToFlag;
|
protected readonly countryCodeToFlag = countryCodeToFlag;
|
||||||
protected readonly Math = Math;
|
protected readonly Math = Math;
|
||||||
protected readonly formatDuration = formatDuration;
|
protected readonly formatDuration = formatDuration;
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export class TextReportService {
|
|||||||
report += `\n\n${this.getStealingReport(similarReplay)}\n`;
|
report += `\n\n${this.getStealingReport(similarReplay)}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
report += `\n\nGenerated on ${site} - [${userDetails.username} profile](${environment.webUrl}/u/${userDetails.user_id})`;
|
report += `\n\nGenerated on ${site} - [${userDetails.username} on ${site}](${environment.webUrl}/u/${userDetails.user_id})`;
|
||||||
|
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
<ng-container *ngIf="this.isError">
|
<ng-container *ngIf="this.isError">
|
||||||
<div class="main term">
|
<div class="main term">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
An error occurred. Maybe try again in a bit?
|
An error occured. Maybe try again in a bit?
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -29,9 +29,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center mt-2">
|
<!-- <div class="text-center mt-2">
|
||||||
<a class="btn" [href]="'https://nise.stedos.dev/replay-viewer/' + this.pair.replays[0].replay_id + '/' + this.pair.replays[1].replay_id" target="_blank">Open in Replay Viewer</a>
|
<a class="btn" [href]="'https://replay.nise.moe/' + this.pair.replays[0].replay_id + '/' + this.pair.replays[1].replay_id" target="_blank">Open in Replay Viewer</a>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<div class="some-page-wrapper text-center">
|
<div class="some-page-wrapper text-center">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
<ng-container *ngIf="this.error">
|
<ng-container *ngIf="this.error">
|
||||||
<div class="main term">
|
<div class="main term">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
An error occurred. Maybe try again in a bit?
|
An error occured. Maybe try again in a bit?
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -53,9 +53,9 @@
|
|||||||
Open in CircleGuard
|
Open in CircleGuard
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a style="flex: 1" class="text-center" [href]="'https://nise.stedos.dev/replay-viewer/' + this.replayData.replay_id" target="_blank" [class.disabled]="!hasReplay()">
|
<!-- <a style="flex: 1" class="text-center" [href]="'https://replay.nise.moe/' + this.replayData.replay_id" target="_blank" [class.disabled]="!hasReplay()">
|
||||||
Open in Replay Viewer
|
Open in Replay Viewer
|
||||||
</a>
|
</a> -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -136,10 +136,6 @@
|
|||||||
<span class="stat-label">PP</span>
|
<span class="stat-label">PP</span>
|
||||||
<span class="stat-value">{{ this.replayData.pp | number: '1.0-0' }}</span>
|
<span class="stat-value">{{ this.replayData.pp | number: '1.0-0' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat" *ngIf="this.replayData.leaderboard_rank">
|
|
||||||
<span class="stat-label">Rank</span>
|
|
||||||
<span class="stat-value">{{ this.replayData.leaderboard_rank }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-container">
|
<div class="stats-container">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
|
|||||||
@ -51,20 +51,6 @@
|
|||||||
<input class="form-control" type="text" id="searchBeatmap" [(ngModel)]="this.filterManager.filters.searchBeatmap" (input)="filterScores()"
|
<input class="form-control" type="text" id="searchBeatmap" [(ngModel)]="this.filterManager.filters.searchBeatmap" (input)="filterScores()"
|
||||||
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Min Rank -->
|
|
||||||
<p>
|
|
||||||
<label for="minRank" class="form-label">Min Rank (of stolen)</label>
|
|
||||||
<input class="form-control" type="number" id="minRank" [(ngModel)]="this.filterManager.filters.minRank" (input)="filterScores()"
|
|
||||||
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Max Rank -->
|
|
||||||
<p>
|
|
||||||
<label for="maxRank" class="form-label">Max Rank (of stolen)</label>
|
|
||||||
<input class="form-control" type="number" id="maxRank" [(ngModel)]="this.filterManager.filters.maxRank" (input)="filterScores()"
|
|
||||||
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
|
||||||
</p>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div *ngIf="getTotalPages() > 1" style="padding: 20px">
|
<div *ngIf="getTotalPages() > 1" style="padding: 20px">
|
||||||
|
|||||||
@ -19,9 +19,6 @@ export interface FilterStolenReplays {
|
|||||||
|
|
||||||
minSimilarity?: number;
|
minSimilarity?: number;
|
||||||
maxSimilarity?: number;
|
maxSimilarity?: number;
|
||||||
|
|
||||||
minRank?: number;
|
|
||||||
maxRank?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -136,12 +133,7 @@ export class ViewSimilarReplaysComponent implements OnInit {
|
|||||||
const similarityMatch = (filters.minSimilarity !== undefined ? score.similarity >= filters.minSimilarity : true) &&
|
const similarityMatch = (filters.minSimilarity !== undefined ? score.similarity >= filters.minSimilarity : true) &&
|
||||||
(filters.maxSimilarity !== undefined ? score.similarity <= filters.maxSimilarity : true);
|
(filters.maxSimilarity !== undefined ? score.similarity <= filters.maxSimilarity : true);
|
||||||
|
|
||||||
const scoreHasRank = score.replay_leaderboard_rank_2 > 0;
|
return usernameMatch && beatmapMatch && ppMatch && similarityMatch;
|
||||||
|
|
||||||
const rankMatch = (filters.minRank == null || (score.replay_leaderboard_rank_2 <= filters.minRank && scoreHasRank)) &&
|
|
||||||
(filters.maxRank == null || score.replay_leaderboard_rank_2 >= filters.maxRank && scoreHasRank);
|
|
||||||
|
|
||||||
return usernameMatch && beatmapMatch && ppMatch && similarityMatch && rankMatch;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filterManager.persistToLocalStorage();
|
this.filterManager.persistToLocalStorage();
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
<input class="form-control" type="number" id="maxPP" [(ngModel)]="this.filterManager.filters.maxPP" (input)="filterScores()"
|
<input class="form-control" type="number" id="maxPP" [(ngModel)]="this.filterManager.filters.maxPP" (input)="filterScores()"
|
||||||
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
||||||
</p>
|
</p>
|
||||||
|
`
|
||||||
<!-- Min cvUR -->
|
<!-- Min cvUR -->
|
||||||
<p>
|
<p>
|
||||||
<label for="minUR" class="form-label">Min cvUR</label>
|
<label for="minUR" class="form-label">Min cvUR</label>
|
||||||
@ -52,20 +52,6 @@
|
|||||||
<input class="form-control" type="text" id="searchBeatmap" [(ngModel)]="this.filterManager.filters.searchBeatmap" (input)="filterScores()"
|
<input class="form-control" type="text" id="searchBeatmap" [(ngModel)]="this.filterManager.filters.searchBeatmap" (input)="filterScores()"
|
||||||
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Min Rank -->
|
|
||||||
<p>
|
|
||||||
<label for="minRank" class="form-label">Min Rank</label>
|
|
||||||
<input class="form-control" type="number" id="minRank" [(ngModel)]="this.filterManager.filters.minRank" (input)="filterScores()"
|
|
||||||
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Max Rank -->
|
|
||||||
<p>
|
|
||||||
<label for="maxRank" class="form-label">Max Rank</label>
|
|
||||||
<input class="form-control" type="number" id="maxRank" [(ngModel)]="this.filterManager.filters.maxRank" (input)="filterScores()"
|
|
||||||
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
|
||||||
</p>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div *ngIf="getTotalPages() > 1" style="padding: 20px">
|
<div *ngIf="getTotalPages() > 1" style="padding: 20px">
|
||||||
|
|||||||
@ -19,9 +19,6 @@ export interface SuspiciousScoresFilter {
|
|||||||
|
|
||||||
searchUsername?: string;
|
searchUsername?: string;
|
||||||
searchBeatmap?: string;
|
searchBeatmap?: string;
|
||||||
|
|
||||||
minRank?: number;
|
|
||||||
maxRank?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -155,12 +152,7 @@ export class ViewSuspiciousScoresComponent implements OnInit, OnDestroy {
|
|||||||
const urMatch = (filters.minUR == null || score.ur >= filters.minUR) &&
|
const urMatch = (filters.minUR == null || score.ur >= filters.minUR) &&
|
||||||
(filters.maxUR == null || score.ur <= filters.maxUR);
|
(filters.maxUR == null || score.ur <= filters.maxUR);
|
||||||
|
|
||||||
const scoreHasRank = score.leaderboard_rank > 0;
|
return usernameMatch && beatmapMatch && ppMatch && urMatch;
|
||||||
|
|
||||||
const rankMatch = (filters.minRank == null || (score.leaderboard_rank <= filters.minRank && scoreHasRank)) &&
|
|
||||||
(filters.maxRank == null || score.leaderboard_rank >= filters.maxRank && scoreHasRank);
|
|
||||||
|
|
||||||
return usernameMatch && beatmapMatch && ppMatch && urMatch && rankMatch;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Presumably persists the current state of filters for future sessions
|
// Presumably persists the current state of filters for future sessions
|
||||||
|
|||||||
@ -298,6 +298,7 @@ fieldset button:not(:last-child) {
|
|||||||
|
|
||||||
fieldset p label {
|
fieldset p label {
|
||||||
display: block;
|
display: block;
|
||||||
|
margin-right: 100px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge.mod {
|
.badge.mod {
|
||||||
|
|||||||
@ -18,24 +18,12 @@ export class DownloadFilesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadCSV(input: Object[], columns: string[], fileName: string = 'data') {
|
downloadCSV(input: Object[], columns: string[], fileName: string = 'data') {
|
||||||
let csvData = columns.join(',') + '\n';
|
const header = columns.join(',') + '\n';
|
||||||
|
|
||||||
for (const row of input) {
|
let csvData = input.map(row =>
|
||||||
let rowData: string[] = [];
|
input.map(row => Object.values(row).join(',')).join('\n')
|
||||||
for (const column of columns) {
|
).join('\n');
|
||||||
let value = (row as Record<string, any>)[column];
|
csvData = header + csvData;
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
value = value.replaceAll(',', ';');
|
|
||||||
} else if (Array.isArray(value)) {
|
|
||||||
value = value.join(';');
|
|
||||||
}
|
|
||||||
|
|
||||||
rowData.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
csvData += rowData.join(',') + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = new Blob([csvData], { type: 'text/csv' });
|
const blob = new Blob([csvData], { type: 'text/csv' });
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
|||||||
@ -63,7 +63,6 @@ services:
|
|||||||
POSTGRES_DB: ${DB_NAME}
|
POSTGRES_DB: ${DB_NAME}
|
||||||
# redis
|
# redis
|
||||||
REDIS_DB: 4
|
REDIS_DB: 4
|
||||||
REDIS_HOST: "redis"
|
|
||||||
# Discord
|
# Discord
|
||||||
WEBHOOK_URL: ${WEBHOOK_URL}
|
WEBHOOK_URL: ${WEBHOOK_URL}
|
||||||
SCORES_WEBHOOK_URL: ${SCORES_WEBHOOK_URL}
|
SCORES_WEBHOOK_URL: ${SCORES_WEBHOOK_URL}
|
||||||
@ -101,10 +100,6 @@ services:
|
|||||||
container_name: nise-frontend
|
container_name: nise-frontend
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
nise-replay-viewer:
|
|
||||||
image: code.stedos.dev/stedos/nise-replay-viewer:latest
|
|
||||||
container_name: nise-replay-viewer
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
@ -35,12 +35,6 @@ http {
|
|||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
}
|
}
|
||||||
|
|
||||||
location /replay-viewer/ {
|
|
||||||
proxy_pass http://nise-replay-viewer/;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
FROM nginx:1.27.0-alpine
|
FROM openresty/openresty:focal
|
||||||
|
|
||||||
RUN rm -rf /usr/share/nginx/html/*
|
RUN rm -rf /usr/share/nginx/html/*
|
||||||
|
|
||||||
|
|||||||
@ -4,17 +4,17 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>/replay/ - nise.stedos.dev</title>
|
<title>/replay/ - nise.moe</title>
|
||||||
<link rel="icon" type="image/x-icon" href="https:/nise.stedos.dev/assets/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="https://nise.moe/assets/favicon.ico">
|
||||||
<!-- Embed data -->
|
<!-- Embed data -->
|
||||||
<meta property="og:title" content="/nise.stedos.dev/ - osu!cheaters finder">
|
<meta property="og:title" content="/nise.moe/ - osu!cheaters finder">
|
||||||
<meta property="og:description" content="crawls osu!std replays and tries to find naughty boys.">
|
<meta property="og:description" content="crawls osu!std replays and tries to find naughty boys.">
|
||||||
<meta property="og:url" content="https://nise.stedos.dev">
|
<meta property="og:url" content="https://nise.moe">
|
||||||
<meta property="og:image" content="https://nise.stedos.dev/assets/banner.png">
|
<meta property="og:image" content="https://nise.moe/assets/banner.png">
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta name="theme-color" content="#151515">
|
<meta name="theme-color" content="#151515">
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
<meta name="twitter:image:src" content="https://nise.stedos.dev/assets/banner.png">
|
<meta name="twitter:image:src" content="https://nise.moe/assets/banner.png">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -18,12 +18,9 @@ export function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// We might be beind a reverse proxy - remove the proxied URL
|
|
||||||
const path = location.pathname.replace("/replay-viewer", "");
|
|
||||||
|
|
||||||
// This pattern matches one or more digits followed by an optional slash and any characters (non-greedy)
|
// This pattern matches one or more digits followed by an optional slash and any characters (non-greedy)
|
||||||
const pathRegex = /^\/(\d+)(?:\/(\d+))?/;
|
const pathRegex = /^\/(\d+)(?:\/(\d+))?/;
|
||||||
const match = path.match(pathRegex);
|
const match = location.pathname.match(pathRegex);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
// match[1] will contain the first ID, match[2] (if present) will contain the second ID
|
// match[1] will contain the first ID, match[2] (if present) will contain the second ID
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export function Navbar() {
|
|||||||
<Menubar className="rounded-none border-x-0 border-t-0 flex justify-between px-4">
|
<Menubar className="rounded-none border-x-0 border-t-0 flex justify-between px-4">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<img src={"https://nise.stedos.dev/assets/keisatsu-chan.png"} width={48}/>
|
<img src={"https://nise.moe/assets/keisatsu-chan.png"} width={48}/>
|
||||||
<h3 className="scroll-m-20 text-lg font-semibold tracking-tight">
|
<h3 className="scroll-m-20 text-lg font-semibold tracking-tight">
|
||||||
/replay/
|
/replay/
|
||||||
</h3>
|
</h3>
|
||||||
@ -24,14 +24,14 @@ export function Navbar() {
|
|||||||
{OsuRenderer.beatmap && (
|
{OsuRenderer.beatmap && (
|
||||||
<>
|
<>
|
||||||
{OsuRenderer.replay2 == null && (
|
{OsuRenderer.replay2 == null && (
|
||||||
<a href={"https://nise.stedos.dev/s/" + OsuRenderer.replay.info.id} target="_blank"
|
<a href={"https://nise.moe/s/" + OsuRenderer.replay.info.id} target="_blank"
|
||||||
className="flex items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent hover:bg-accent">
|
className="flex items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent hover:bg-accent">
|
||||||
View on nise.stedos.dev
|
View on nise.moe
|
||||||
</a>)}
|
</a>)}
|
||||||
{OsuRenderer.replay2 && (
|
{OsuRenderer.replay2 && (
|
||||||
<a href={"https://nise.stedos.dev/p/" + OsuRenderer.replay.info.id + "/" + OsuRenderer.replay2.info.id} target="_blank"
|
<a href={"https://nise.moe/p/" + OsuRenderer.replay.info.id + "/" + OsuRenderer.replay2.info.id} target="_blank"
|
||||||
className="flex items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent hover:bg-accent">
|
className="flex items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent hover:bg-accent">
|
||||||
View on nise.stedos.dev
|
View on nise.moe
|
||||||
</a>)}
|
</a>)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export class Drawer {
|
|||||||
|
|
||||||
static async loadDefaultImages() {
|
static async loadDefaultImages() {
|
||||||
const imageLoadPromises = Object.keys(Drawer.images).map(imageName =>
|
const imageLoadPromises = Object.keys(Drawer.images).map(imageName =>
|
||||||
loadImageAsync(`/replay-viewer/${imageName}.png`).then(
|
loadImageAsync(`/${imageName}.png`).then(
|
||||||
image => {
|
image => {
|
||||||
Drawer.images[imageName as keyof typeof Drawer.images] = image;
|
Drawer.images[imageName as keyof typeof Drawer.images] = image;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -231,7 +231,7 @@ export class OsuRenderer {
|
|||||||
static getApiUrl(): string {
|
static getApiUrl(): string {
|
||||||
return document.location.hostname === "localhost"
|
return document.location.hostname === "localhost"
|
||||||
? `http://localhost:8080`
|
? `http://localhost:8080`
|
||||||
: `https://nise.stedos.dev/api`;
|
: `https://nise.moe/api`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async loadReplayPairFromUrl(replayId1: number, replayId2: number) {
|
static async loadReplayPairFromUrl(replayId1: number, replayId2: number) {
|
||||||
|
|||||||
@ -9,5 +9,4 @@ export default defineConfig({
|
|||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
base: "/replay-viewer/",
|
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user