Skip to content

Commit daedcfb

Browse files
committed
feat(v3/api): Update to 20-November-2025 revision
1 parent 29d8334 commit daedcfb

File tree

8 files changed

+67
-62
lines changed

8 files changed

+67
-62
lines changed

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/AddressResult.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ data class AddressResult(
5757
DeviceEstimate.CODEC.fieldOf("device_estimate").forGetter(AddressResult::deviceEstimate),
5858
Detections.CODEC.fieldOf("detections").forGetter(AddressResult::detections),
5959
Operator.CODEC.optionalFieldOf("operator").forNullableGetter(AddressResult::operator),
60-
Codecs.INSTANT_EPOCH_SECONDS.fieldOf("last_updated").forGetter(AddressResult::lastUpdatedAt),
60+
Codecs.INSTANT_ISO_8601.fieldOf("last_updated").forGetter(AddressResult::lastUpdatedAt),
6161
).apply(instance, ::AddressResult)
6262
}
6363

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Detections.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import com.mojang.serialization.Codec
44
import org.jetbrains.annotations.ApiStatus
55
import org.jetbrains.annotations.Range
66
import ru.epserv.proxycheck.v3.api.util.buildMapCodec
7+
import ru.epserv.proxycheck.v3.api.util.codec.Codecs
8+
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.forNullableGetter
9+
import java.util.Optional
10+
import kotlin.jvm.optionals.getOrNull
11+
import kotlin.time.ExperimentalTime
12+
import kotlin.time.Instant
713

814
/**
915
* Detections information.
@@ -16,9 +22,15 @@ import ru.epserv.proxycheck.v3.api.util.buildMapCodec
1622
* @property hosting whether the IP is detected as a hosting provider
1723
* @property anonymous whether the IP is detected as anonymous
1824
* @property risk risk score of the IP (0-100)
25+
* @property confidence confidence score of the detection (0-100)
26+
* @property firstSeen the first time the IP was seen acting as a proxy/VPN/etc.,
27+
* or `null` if undetected/not available
28+
* @property lastSeen the last time the IP was seen acting as a proxy/VPN/etc.,
29+
* or `null` if undetected/not available
1930
* @since 1.0.0
2031
* @author metabrix
2132
*/
33+
@OptIn(ExperimentalTime::class)
2234
@ApiStatus.AvailableSince("1.0.0")
2335
data class Detections(
2436
val proxy: Boolean,
@@ -29,7 +41,35 @@ data class Detections(
2941
val hosting: Boolean,
3042
val anonymous: Boolean,
3143
val risk: @Range(from = 0, to = 100) Int,
44+
val confidence: @Range(from = 0, to = 100) Int,
45+
val firstSeen: Instant?,
46+
val lastSeen: Instant?,
3247
) {
48+
constructor(
49+
proxy: Boolean,
50+
vpn: Boolean,
51+
compromised: Boolean,
52+
scraper: Boolean,
53+
tor: Boolean,
54+
hosting: Boolean,
55+
anonymous: Boolean,
56+
risk: @Range(from = 0, to = 100) Int,
57+
confidence: @Range(from = 0, to = 100) Int,
58+
firstSeen: Optional<Instant>,
59+
lastSeen: Optional<Instant>,
60+
) : this(
61+
proxy = proxy,
62+
vpn = vpn,
63+
compromised = compromised,
64+
scraper = scraper,
65+
tor = tor,
66+
hosting = hosting,
67+
anonymous = anonymous,
68+
risk = risk,
69+
confidence = confidence,
70+
firstSeen = firstSeen.getOrNull(),
71+
lastSeen = lastSeen.getOrNull(),
72+
)
3373
companion object {
3474
@ApiStatus.Internal
3575
internal val CODEC = buildMapCodec { instance ->
@@ -42,6 +82,9 @@ data class Detections(
4282
Codec.BOOL.fieldOf("hosting").forGetter(Detections::hosting),
4383
Codec.BOOL.fieldOf("anonymous").forGetter(Detections::anonymous),
4484
Codec.intRange(0, 100).optionalFieldOf("risk", 0).forGetter(Detections::risk),
85+
Codec.intRange(0, 100).fieldOf("confidence").forGetter(Detections::confidence),
86+
Codecs.INSTANT_ISO_8601.optionalFieldOf("first_seen").forNullableGetter(Detections::firstSeen),
87+
Codecs.INSTANT_ISO_8601.optionalFieldOf("last_seen").forNullableGetter(Detections::lastSeen),
4588
).apply(instance, ::Detections)
4689
}
4790
}

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Location.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import com.mojang.serialization.Codec
44
import org.jetbrains.annotations.ApiStatus
55
import ru.epserv.proxycheck.v3.api.util.buildMapCodec
66
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.forNullableGetter
7-
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.orNullIf
87
import java.util.*
98
import kotlin.jvm.optionals.getOrNull
109

@@ -77,13 +76,13 @@ data class Location(
7776
Codec.STRING.fieldOf("continent_code").forGetter(Location::continentCode),
7877
Codec.STRING.fieldOf("country_name").forGetter(Location::countryName),
7978
Codec.STRING.fieldOf("country_code").forGetter(Location::countryCode),
80-
Codec.STRING.fieldOf("region_name").orNullIf("unknown", ignoreCase = true).forNullableGetter(Location::regionName),
81-
Codec.STRING.fieldOf("region_code").orNullIf("unknown", ignoreCase = true).forNullableGetter(Location::regionCode),
82-
Codec.STRING.fieldOf("city_name").orNullIf("unknown", ignoreCase = true).forNullableGetter(Location::cityName),
83-
Codec.STRING.optionalFieldOf("postal_code").orNullIf("unknown", ignoreCase = true).forNullableGetter(Location::postalCode),
79+
Codec.STRING.optionalFieldOf("region_name").forNullableGetter(Location::regionName),
80+
Codec.STRING.optionalFieldOf("region_code").forNullableGetter(Location::regionCode),
81+
Codec.STRING.optionalFieldOf("city_name").forNullableGetter(Location::cityName),
82+
Codec.STRING.optionalFieldOf("postal_code").forNullableGetter(Location::postalCode),
8483
Codec.DOUBLE.fieldOf("latitude").forGetter(Location::latitude),
8584
Codec.DOUBLE.fieldOf("longitude").forGetter(Location::longitude),
86-
Codec.STRING.fieldOf("timezone").orNullIf("unknown", ignoreCase = true).forNullableGetter(Location::timeZone),
85+
Codec.STRING.optionalFieldOf("timezone").forNullableGetter(Location::timeZone),
8786
Currency.CODEC.fieldOf("currency").forGetter(Location::currency),
8887
).apply(instance, ::Location)
8988
}

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Network.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import ru.epserv.proxycheck.v3.api.model.common.CidrIpRange
66
import ru.epserv.proxycheck.v3.api.util.buildMapCodec
77
import ru.epserv.proxycheck.v3.api.util.codec.Codecs
88
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.forNullableGetter
9-
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.orNullIf
109
import java.util.*
1110
import kotlin.jvm.optionals.getOrNull
1211

@@ -51,11 +50,11 @@ data class Network(
5150
@ApiStatus.Internal
5251
internal val CODEC = buildMapCodec { instance ->
5352
instance.group(
54-
Codecs.ASN_STRING.fieldOf("asn").orNullIf("unknown", ignoreCase = true).forNullableGetter(Network::asn),
55-
CidrIpRange.STRING_CODEC.fieldOf("range").orNullIf("unknown", ignoreCase = true).forNullableGetter(Network::range),
56-
Codec.STRING.optionalFieldOf("hostname").orNullIf("unknown", ignoreCase = true).forNullableGetter(Network::hostName),
57-
Codec.STRING.fieldOf("provider").orNullIf("unknown", ignoreCase = true).forNullableGetter(Network::provider),
58-
Codec.STRING.fieldOf("organisation").orNullIf("unknown", ignoreCase = true).forNullableGetter(Network::organisation),
53+
Codecs.ASN_STRING.optionalFieldOf("asn").forNullableGetter(Network::asn),
54+
CidrIpRange.STRING_CODEC.optionalFieldOf("range").forNullableGetter(Network::range),
55+
Codec.STRING.optionalFieldOf("hostname").forNullableGetter(Network::hostName),
56+
Codec.STRING.optionalFieldOf("provider").forNullableGetter(Network::provider),
57+
Codec.STRING.optionalFieldOf("organisation").forNullableGetter(Network::organisation),
5958
Codec.STRING.fieldOf("type").forGetter(Network::type),
6059
).apply(instance, ::Network)
6160
}

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Operator.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import com.mojang.serialization.Codec
44
import org.jetbrains.annotations.ApiStatus
55
import ru.epserv.proxycheck.v3.api.util.buildMapCodec
66
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.forNullableGetter
7-
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.orNullIf
87
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.setOf
98
import java.util.*
109
import kotlin.jvm.optionals.getOrNull
@@ -52,8 +51,8 @@ data class Operator(
5251
instance.group(
5352
Codec.STRING.fieldOf("name").forGetter(Operator::name),
5453
Codec.STRING.fieldOf("url").forGetter(Operator::url),
55-
Codec.STRING.optionalFieldOf("anonymity").orNullIf("unknown", ignoreCase = true).forNullableGetter(Operator::anonymity),
56-
Codec.STRING.optionalFieldOf("popularity").orNullIf("unknown", ignoreCase = true).forNullableGetter(Operator::popularity),
54+
Codec.STRING.optionalFieldOf("anonymity").forNullableGetter(Operator::anonymity),
55+
Codec.STRING.optionalFieldOf("popularity").forNullableGetter(Operator::popularity),
5756
Codec.STRING.setOf().fieldOf("protocols").forGetter(Operator::protocols),
5857
OperatorPolicies.CODEC.optionalFieldOf("policies", OperatorPolicies.UNKNOWN).forGetter(Operator::policies),
5958
).apply(instance, ::Operator)

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/util/Extensions.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import com.mojang.serialization.MapCodec
88
import com.mojang.serialization.codecs.OptionalFieldCodec
99
import com.mojang.serialization.codecs.RecordCodecBuilder
1010
import java.net.URLEncoder
11-
import java.util.*
1211
import kotlin.reflect.KProperty1
1312
import kotlin.reflect.full.declaredMemberProperties
1413
import kotlin.reflect.jvm.isAccessible
@@ -64,11 +63,6 @@ internal operator fun ByteArray.compareTo(other: ByteArray): Int {
6463
?: this.size.compareTo(other.size)
6564
}
6665

67-
internal fun <T, R> Optional<T>.mapOrElse(
68-
ifPresent: (T) -> R,
69-
ifEmpty: () -> R,
70-
): R = this.map(ifPresent).orElseGet(ifEmpty)
71-
7266

7367
private val optionalFieldCodecNameProperty = fieldNameProperty<OptionalFieldCodec<*>>()
7468

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/util/codec/Codecs.kt

Lines changed: 10 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package ru.epserv.proxycheck.v3.api.util.codec
22

3-
import com.mojang.datafixers.util.Either
43
import com.mojang.serialization.Codec
54
import com.mojang.serialization.DataResult
65
import com.mojang.serialization.MapCodec
76
import com.mojang.serialization.codecs.RecordCodecBuilder
8-
import ru.epserv.proxycheck.v3.api.util.mapOrElse
9-
import ru.epserv.proxycheck.v3.api.util.name
107
import java.net.InetAddress
118
import java.util.*
129
import kotlin.time.ExperimentalTime
@@ -26,7 +23,16 @@ internal object Codecs {
2623
{ asn -> "AS$asn" },
2724
)
2825

29-
val INSTANT_EPOCH_SECONDS: Codec<Instant> = Codec.LONG.xmap(Instant::fromEpochSeconds, Instant::epochSeconds)
26+
val INSTANT_ISO_8601: Codec<Instant> = Codec.STRING.comapFlatMap(
27+
{ iso8601String ->
28+
try {
29+
DataResult.success(Instant.parse(iso8601String))
30+
} catch (e: Exception) {
31+
DataResult.error { "Failed to decode Instant from ISO 8601 string '$iso8601String': ${e.message}" }
32+
}
33+
},
34+
{ instant -> instant.toString() },
35+
)
3036

3137
fun <O : Any, A : Any> MapCodec<Optional<A>>.forNullableGetter(getter: (O) -> A?): RecordCodecBuilder<O, Optional<A>> {
3238
return this.forGetter { obj -> Optional.ofNullable(getter(obj)) }
@@ -38,41 +44,6 @@ internal object Codecs {
3844
return MapCodec.assumeMapUnsafe(Codec.unboundedMap(this, valueCodec))
3945
}
4046

41-
fun Codec<String>.constant(constantValue: String, ignoreCase: Boolean = false): Codec<String> {
42-
fun transform(providedValue: String): DataResult<String> {
43-
if (providedValue.equals(constantValue, ignoreCase)) {
44-
return DataResult.success(providedValue)
45-
}
46-
return DataResult.error { "Expected constant value '$constantValue' (ignoreCase = $ignoreCase), but got '$providedValue'" }
47-
}
48-
49-
return this.flatXmap(::transform, ::transform)
50-
}
51-
52-
@JvmName("orNullIfMapCodec")
53-
fun <A : Any> MapCodec<A>.orNullIf(nullValue: String, ignoreCase: Boolean = false): MapCodec<Optional<A>> {
54-
val fieldName = requireNotNull(this.name) { "MapCodec must have a name to use orNullIf" }
55-
return Codec.mapEither(
56-
Codec.STRING.constant(nullValue, ignoreCase).optionalFieldOf(fieldName),
57-
this,
58-
).xmap(
59-
{ either -> either.map({ Optional.empty() }, { value -> Optional.of(value) }) },
60-
{ optional -> optional.mapOrElse({ value -> Either.right(value) }, { Either.left(Optional.empty()) }) },
61-
)
62-
}
63-
64-
@JvmName("orNullIfMapCodecOptional")
65-
fun <A : Any> MapCodec<Optional<A>>.orNullIf(nullValue: String, ignoreCase: Boolean = false): MapCodec<Optional<A>> {
66-
val fieldName = requireNotNull(this.name) { "MapCodec must have a name to use orNullIf" }
67-
return Codec.mapEither(
68-
Codec.STRING.constant(nullValue, ignoreCase).optionalFieldOf(fieldName),
69-
this,
70-
).xmap(
71-
{ either -> either.map({ Optional.empty() }, { value -> value }) },
72-
{ optional -> optional.mapOrElse({ value -> Either.right(Optional.of(value)) }, { Either.left(Optional.empty()) }) }
73-
)
74-
}
75-
7647
fun decodeInetAddress(string: String): DataResult<InetAddress> {
7748
return this.decodeInet4Address(string).orElseGet { errorV4 ->
7849
this.decodeInet6Address(string).orElseGet { errorV6 ->

v3/impl-java/src/main/kotlin/ru/epserv/proxycheck/v3/impl/ProxyCheckApiImplConfiguration.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,6 @@ data class ProxyCheckApiImplConfiguration(
105105
@ApiStatus.AvailableSince("1.0.0")
106106
data class UnsupportedConfiguration(
107107
@ApiStatus.AvailableSince("1.0.0")
108-
var apiVersion: String = "12-August-2025",
108+
var apiVersion: String = "20-November-2025",
109109
)
110110
}

0 commit comments

Comments
 (0)