Added ktor site.

This commit is contained in:
yenon 2023-11-06 16:19:07 +01:00
parent 8d90282588
commit bdcac0f494
8 changed files with 270 additions and 52 deletions

6
.idea/kotlinc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.20" />
</component>
</project>

View File

@ -1,5 +1,7 @@
plugins { plugins {
id("java") id("java")
kotlin("jvm") version "1.9.20"
kotlin("plugin.serialization") version "1.9.0"
} }
group = "org.example" group = "org.example"
@ -12,8 +14,20 @@ repositories {
dependencies { dependencies {
testImplementation(platform("org.junit:junit-bom:5.9.1")) testImplementation(platform("org.junit:junit-bom:5.9.1"))
testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.junit.jupiter:junit-jupiter")
implementation(kotlin("stdlib-jdk8"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("io.ktor:ktor-server-core-jvm:2.3.5")
implementation("io.ktor:ktor-server-cio-jvm:2.3.5")
implementation("io.ktor:ktor-server-status-pages-jvm:2.3.5")
implementation("io.ktor:ktor-server-default-headers-jvm:2.3.5")
implementation("io.ktor:ktor-client-core:2.3.5")
implementation("io.ktor:ktor-client-cio:2.3.5")
implementation("io.ktor:ktor-server-html-builder:2.3.5")
} }
tasks.test { tasks.test {
useJUnitPlatform() useJUnitPlatform()
}
kotlin {
jvmToolchain(11)
} }

View File

@ -1,2 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
}
rootProject.name = "VeloDecode" rootProject.name = "VeloDecode"

View File

@ -1,52 +0,0 @@
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Base64;
public class Main {
private static final byte[] key = "BatCaveGGevaCtaB".getBytes(Charset.defaultCharset());
public static String decodeVeloBs(String input) {
try {
var encoded = Base64.getDecoder().decode(input);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE,new SecretKeySpec(key, "AES"));
return new String(cipher.doFinal(encoded));
}catch (Exception ignored){}
return null;
}
public static String encodeVeloBs(String input){
try {
var encoded = input.getBytes();
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE,new SecretKeySpec(key, "AES"));
return new String(Base64.getEncoder().encode(cipher.doFinal(encoded)));
}catch (Exception ignored){}
return null;
}
public static void main(String[] args){
Security.setProperty("crypto.policy", "unlimited");
//URL url = new URL("http://www.velocidrone.com/api/leaderboard/getLeaderBoard");
//HttpURLConnection veloConnection = (HttpURLConnection) url.openConnection();
//veloConnection.setDoInput(true);
//veloConnection.setDoOutput(true);
//veloConnection.connect();
//var writer = new OutputStreamWriter(veloConnection.getOutputStream());
//writer.write("post_data=wQb/V5e6u70LZd5g1C1ZulicB4/KTXGAErDvWW0VdpuxrJf87bxEqN4dq55t2m5t530teJLczZM31JJLVwDMzKx9jvmxQNZ/Vo1abXxWamEpvmRRF1fHw2rOFK98VpWP");
//var reader = new BufferedReader(new InputStreamReader(veloConnection.getInputStream()));
//var baseString = "0afWszJKVVHqYD1vYdg8+z8E0/Jg7QSa/J+OMhUi4TY6+CoMRHLVpH88cg0DnRGaVtn54YlVzChaTgkFVXPKzeAMK8z7AIzg8ZpVPoAJGH1pHFaHM/6ZHX1W+Uakv1MW+FztGI+hgszogU4DcQFWNstBgOD33yBxJlhjhBNj6V0qxfJaX2lG+04pyon9JzPAS7qqsSo2WYNsfzXdHfU0QgkbUo23+8sglAPwHyJZqnLA7FZmtYsIXQSWnljppoDucwuXc2zZdhLzdsIXPJ/WK9VMEafQ9VbE4UrXzdNSM8cclguFk57rUulw0qwJZLDTlV5GoV4wyJj07TVKIMHKUO9nBpYOZNHac7TdAdV8yX1gWvK/dJmhem/YIWgTc/V70MncZ5zSsTXT74/2SDzptTDdeuRws9mcGA7Tk8SCbwxCHEJstoH0OHsmvl6tPaBZjZ3oyaIf7hLv+t1Qx9thm6WJh/hiatrWcCte/TAJz6lbDDMXplP2llEY7Up+sOeaf/LmIpV5YA4LHz0rQUCuqDv/3x7cPdeIW3P+0FXv8kVRq1w+RGKXF9E+KMK7pSGDhBmTmi/+lb+vfeSvm+cqY3gXao6eyyY4nUA1xKTh+nHp16cW7vFNWLSW/OS78Fz4AaUSBIh9N4RitBeRaUZGAX/y5iKVeWAOCx89K0FArqg7/98e3D3XiFtz/tBV7/JFUatcPkRilxfRPijCu6Uhg4QZk5ov/pW/r33kr5vnKmMnY6uIBccjRlbxEHDV/Pxk4yolUn09qYCD/QF8meoxb/YRouOdqcF3dM4YCQFiiHuBAUqWjmmS6ORk2+s6QMZq6rOkhIfX3nQBwCAaFsdGauo1i7w+8JMNAPGgFdBWa5qdmxfQ4koiLIIcAA0ZjeMLVl9odLt/hUntHti9gP9G8S6Z//YlaHcgV7zU+WWHWz8F3HlD3226SRRgtKpkfKBCAaUSBIh9N4RitBeRaUZGAX/y5iKVeWAOCx89K0FArqg7/98e3D3XiFtz/tBV7/JFUatcPkRilxfRPijCu6Uhg4QZk5ov/pW/r33kr5vnKmPOJUtlqH05/GyBWp8GY++ZkoGkBytjKq9jmkXvkvjlSUArwxm9g1VVzl51aKwMXd6G0wrErJZcmwmiIpB2tNqsjH5Ks+/n8n3Q7veteeIq3LoGVfxvLsVfrKyOpKjCKanjSrRYBI2kHucrqJYjAG1CXOHrL0nfciXcIE6SFb/waxeHLNdDlQvi9RlAH9Sc76vvZwaWDmTR2nO03QHVfMl94ZqyVQvQc6b57RFVpqMlH9DJ3Gec0rE10++P9kg86bUw3XrkcLPZnBgO05PEgm8MQhxCbLaB9Dh7Jr5erT2gWbYOhsYV7FEfaVYHAX4BeREWndxjPEeRZbI6nwwK8D+6+BsCXzW25lwYpbTpvTXcVUTCUmBL5LmPZ/7AQpQ/7FNuXcmr3sGDCU6EqoG4E5rlVVXVVCmGwj0eDfRz6/ZOsYaqtgZOpXtP6hTXUXVSYXY=";
//var baseString = "wQb/V5e6u70LZd5g1C1ZulicB4/KTXGAErDvWW0VdpuxrJf87bxEqN4dq55t2m5t530teJLczZM31JJLVwDMzKx9jvmxQNZ/Vo1abXxWamEpvmRRF1fHw2rOFK98VpWP";
var baseString = "dv+uxe7a/WVJ2zEC/uXbjeaw7sHDsRrXYe8gBH5VzCI44PFFIZuoHRbeSDNDSkJBGBybgGJogLtg0tT2apZEWJriB98GjeeZV7Gh7qaNXOxOLH9+Snnc9NPpqNqMTRLiGjR1pvvX9rjJXQAF+d2DrMtsB4RsbyKiJBPeWxWiI6jaPtyDG5E6fS/ErOTHQnxDH7JIhFBwTg0p8FVvIBlNoQ==";
System.out.println(decodeVeloBs(baseString));
}
}

19
src/main/java/Main.kt Normal file
View File

@ -0,0 +1,19 @@
import io.ktor.server.application.*
import io.ktor.server.cio.*
import io.ktor.server.engine.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun main(){
val server = embeddedServer(CIO, port = 8080){
routing {
get("/"){
call.respondRedirect("/search")
}
searchPage()
trackPage()
}
}
println("connection to localhost:8080 now possible.")
server.start(true)
}

View File

@ -0,0 +1,89 @@
import io.ktor.server.application.*
import io.ktor.server.html.*
import io.ktor.server.request.*
import io.ktor.server.routing.*
import io.ktor.util.pipeline.*
import kotlinx.html.*
fun Routing.searchPage(){
get("/search"){
searchPage()
}
post("/search"){
val parameters = call.receiveParameters()
val author = parameters["author"] ?: ""
val track = parameters["track"] ?: ""
val results = Velocidrone.getTrackResults(track,author)
results.getOrNull()?.let {
searchResultPage(it)
}
}
}
private suspend fun PipelineContext<Unit,ApplicationCall>.searchPage(){
call.respondHtml {
head {
title {
+"Velocidrone track search"
}
}
body {
form("/search", encType = FormEncType.applicationXWwwFormUrlEncoded, method = FormMethod.post){
p{
+"Trackname:"
textInput(name = "track")
}
p{
+"Author:"
textInput(name = "author")
}
p{
submitInput()
}
}
}
}
}
private suspend fun PipelineContext<Unit,ApplicationCall>.searchResultPage(trackResults: Velocidrone.TrackResults){
call.respondHtml {
head {
title {
+"Search results"
}
}
body {
table {
thead {
tr {
td {
+"MapID"
}
td {
+"Name"
}
td {
+"Author"
}
}
}
trackResults.user_tracks.forEach {
tr {
td {
a("/track/"+it.id){
+it.id.toString()
}
}
td {
+it.track_name
}
td {
+it.playername
}
}
}
}
}
}
}

View File

@ -0,0 +1,42 @@
import io.ktor.server.application.*
import io.ktor.server.html.*
import io.ktor.server.routing.*
import io.ktor.util.pipeline.*
import kotlinx.html.*
fun Routing.trackPage() {
get("/track/{id}"){
call.parameters.get("id")?.toLong()?.let {
val result = Velocidrone.getLeaderboardForId(it)
result.getOrNull()?.let {
trackPage(it)
}
}
}
}
private suspend fun PipelineContext<Unit, ApplicationCall>.trackPage(leaderboard: Velocidrone.Leaderboard){
call.respondHtml {
head {
title {
+"Leaderboard"
}
}
body {
table {
thead {
tr {
td { +"Time" }
td { +"Name" }
}
}
leaderboard.tracktimes.sortedBy { it.lap_time }.forEach {
tr {
td { +it.lap_time.toString() }
td { +it.playername }
}
}
}
}
}
}

View File

@ -0,0 +1,97 @@
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.net.URLEncoder
import java.nio.charset.Charset
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
object Velocidrone{
private val key = "BatCaveGGevaCtaB".toByteArray(Charset.defaultCharset())
fun decodeVeloBase64(input: ByteArray): Result<String> {
val decoded = Base64.getDecoder().decode(input)
val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"))
return Result.success(String(cipher.doFinal(decoded)))
}
fun encodeVeloBase64(input: String): ByteArray {
val encoded: ByteArray = input.toByteArray()
val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"))
return Base64.getEncoder().encode(cipher.doFinal(encoded))
}
val client = HttpClient(CIO)
@Serializable
data class Leaderboard(
val success:Boolean,
val message_title:String,
val message:String,
val tracktimes:Array<TrackTime>
)
@Serializable
data class TrackTime(
val lap_time:Float,
val playername: String,
val model_id: Long,
val country: String,
val sim_version: String,
val device_type: Long
)
suspend fun getLeaderboardForId(id:Long): Result<Leaderboard> {
//track_id=21192&sim_version=1.16&offset=0&count=400&protected_track_value=2&race_mode=6
val packet = "track_id=$id&sim_version=1.16&offset=0&count=400&protected_track_value=2&race_mode=6"
val result = decodeVeloBase64(client.post("https://www.velocidrone.com/api/leaderboard/getLeaderBoard"){
val string = "post_data="+URLEncoder.encode(String(encodeVeloBase64(packet)), Charsets.UTF_8)
setBody(string)
setAttributes {
headers["Content-Type"]="application/x-www-form-urlencoded"
}
}.body())
return Result.success(Json.decodeFromString<Leaderboard>(result.getOrThrow()))
}
@Serializable
data class TrackResults(
val success:Boolean,
val detail:String,
val user_tracks:Array<Track>
)
@Serializable
data class Track(
val id:Long,
val scenery_id:Long,
val track_name:String,
val track_type:String,
val playername:String,
val rating:Float,
val total_ratings:Long,
val date:String
)
suspend fun getTrackResults(name:String, author:String): Result<TrackResults> {
//scenery_id=&playername=&track_name=&track_type=&order_by_date=True&order_by_rating=True&show_beginner=True&show_intermediate=True&show_advanced=True
//scenery_id=&playername=stimpyten&track_name=&track_type=&order_by_date=True&order_by_rating=True&show_beginner=True&show_intermediate=True&show_advanced=True
val packet = "scenery_id=&playername=$author&track_name=$name&track_type=&order_by_date=True&order_by_rating=True&show_beginner=True&show_intermediate=True&show_advanced=True"
return Result.success(Json.decodeFromString<TrackResults>(decodeVeloBase64(client.post("https://www.velocidrone.com/api/v1/private/user/rated_tracks_list"){
val string = "post_data="+URLEncoder.encode(String(encodeVeloBase64(packet)), Charsets.UTF_8)
setBody(string)
setAttributes {
headers["Content-Type"]="application/x-www-form-urlencoded"
}
}.body()).getOrThrow()))
}
}