This commit is contained in:
2025-12-14 23:03:10 +01:00
parent cc37d6bcb0
commit 4a8244466e
10 changed files with 74 additions and 107 deletions

View File

@@ -1,13 +1,11 @@
package com.infendro.ddns
import io.ktor.client.HttpClient
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
object Http {
val client = HttpClient {
val http = HttpClient {
install(ContentNegotiation) {
json()
}
}
}

View File

@@ -13,7 +13,7 @@ import com.infendro.ddns.Argument.SUBDOMAIN
import com.infendro.ddns.argument.ipService
import com.infendro.ddns.config.Config
import com.infendro.ddns.dns.DnsService
import com.infendro.ddns.dns.DnsServices.PORKBUN
import com.infendro.ddns.dns.DnsService.Companion.PORKBUN
import com.infendro.ddns.dns.model.DnsRecord
import com.infendro.ddns.dns.porkbun.Porkbun
import com.infendro.ddns.ip.IpService

View File

@@ -3,13 +3,16 @@ package com.infendro.ddns.argument
import com.infendro.cli.argument.Argument
import com.infendro.cli.command.Command
import com.infendro.ddns.ip.IpService
import com.infendro.ddns.ip.IpServices
import com.infendro.ddns.ip.ipify.Ipify
object IpServiceParser : Argument.Parser<IpService> {
override fun parse(
text: String?,
): IpService {
return IpServices.get(text!!)
return when (text) {
IpService.IPIFY -> Ipify
else -> throw Exception()
}
}
}
@@ -17,7 +20,7 @@ class IpServiceOrDefaultArgument(
override val name: String,
) : Argument.SingleOrElse<IpService>() {
override val other: IpService
get() = IpServices.DEFAULT
get() = IpService.DEFAULT
override val parser: Parser<IpService>
get() = IpServiceParser

View File

@@ -2,13 +2,13 @@ package com.infendro.ddns.dns
import com.infendro.ddns.dns.model.DnsRecord
abstract class DnsService {
abstract fun getRecords(
interface DnsService {
fun getRecords(
domain: String,
subdomains: List<String>,
): List<DnsRecord>
abstract fun updateRecord(
fun updateRecord(
record: DnsRecord,
value: String,
)
@@ -22,4 +22,8 @@ abstract class DnsService {
updateRecord(record, value)
}
}
companion object {
const val PORKBUN = "porkbun"
}
}

View File

@@ -1,5 +0,0 @@
package com.infendro.ddns.dns
object DnsServices {
const val PORKBUN = "porkbun"
}

View File

@@ -1,5 +0,0 @@
package com.infendro.ddns.dns
object Regex {
val DOMAIN = Regex("""(?:(?<subdomain>(?:\w+\.)*\w+)\.)?(?<domain>\w+\.\w+)""")
}

View File

@@ -1,36 +1,52 @@
package com.infendro.ddns.dns.porkbun
import com.infendro.ddns.Http.client
import com.infendro.ddns.dns.Regex.DOMAIN
import com.infendro.ddns.dns.DnsService
import com.infendro.ddns.dns.model.DnsRecord
import com.infendro.ddns.dns.porkbun.dto.request.GetRecordsRequest
import com.infendro.ddns.dns.porkbun.dto.request.UpdateRecordRequest
import com.infendro.ddns.dns.porkbun.dto.response.GetRecordsResponse
import io.ktor.client.call.body
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import com.infendro.ddns.http
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.coroutines.runBlocking
class Porkbun(
val key: String,
val secretkey: String,
) : DnsService() {
) : DnsService {
override fun getRecords(
domain: String,
subdomains: List<String>,
): List<DnsRecord> = runBlocking {
val response = client.post("https://api.porkbun.com/api/json/v3/dns/retrieve/$domain") {
val request = getRecordsRequest()
val response = http.post("https://api.porkbun.com/api/json/v3/dns/retrieve/$domain") {
val request = GetRecordsRequest(
apikey = key,
secretapikey = secretkey,
)
contentType(ContentType.Application.Json)
setBody(request)
}
val body = response.body<GetRecordsResponse>()
body.records
.map { model(it) }
.map {
val labels = it.name.split('.')
val subdomain = labels.dropLast(2).joinToString(".")
val domain = labels.takeLast(2).joinToString(".")
DnsRecord(
id = it.id,
type = DnsRecord.Type.valueOf(it.type),
domain = DnsRecord.Domain(
subdomain = subdomain,
domain = domain,
),
value = it.content,
ttl = it.ttl.toInt(),
priority = it.prio?.toInt(),
)
}
.filter { it.type in DnsRecord.Type.address && (it.domain.subdomain ?: "") in subdomains }
}
@@ -38,51 +54,17 @@ class Porkbun(
record: DnsRecord,
value: String,
): Unit = runBlocking {
client.post("https://api.porkbun.com/api/json/v3/dns/edit/${record.domain.domain}/${record.id}") {
val request = updateRecordRequest(record, value)
contentType(ContentType.Application.Json)
setBody(request)
}
}
private fun model(
record: GetRecordsResponse.Record,
): DnsRecord {
val match = DOMAIN.matchEntire(record.name)!!
val domain = match.groups["domain"]!!.value
val subdomain = match.groups["subdomain"]?.value
return DnsRecord(
id = record.id,
type = DnsRecord.Type.valueOf(record.type),
domain = DnsRecord.Domain(
subdomain = subdomain,
domain = domain,
),
value = record.content,
ttl = record.ttl.toInt(),
priority = record.prio?.toInt(),
)
}
private fun getRecordsRequest(): GetRecordsRequest {
return GetRecordsRequest(
apikey = key,
secretapikey = secretkey,
)
}
private fun updateRecordRequest(
record: DnsRecord,
ip: String,
): UpdateRecordRequest {
return UpdateRecordRequest(
http.post("https://api.porkbun.com/api/json/v3/dns/edit/${record.domain.domain}/${record.id}") {
val request = UpdateRecordRequest(
apikey = key,
secretapikey = secretkey,
type = record.type.toString(),
name = record.domain.subdomain ?: "",
content = ip,
content = value,
ttl = record.ttl.toString(),
)
contentType(ContentType.Application.Json)
setBody(request)
}
}
}

View File

@@ -1,9 +1,16 @@
package com.infendro.ddns.ip
abstract class IpService {
abstract val ipv4: String
abstract val ipv6: String
import com.infendro.ddns.ip.ipify.Ipify
val ip: List<String>
get() = listOf(ipv4, ipv6)
interface IpService {
val ipv4: String
val ipv6: String
val ip: Pair<String, String>
get() = Pair(ipv4, ipv6)
companion object {
const val IPIFY = "ipify"
val DEFAULT = Ipify
}
}

View File

@@ -1,17 +0,0 @@
package com.infendro.ddns.ip
import com.infendro.ddns.ip.ipify.Ipify
object IpServices {
const val IPIFY = "ipify"
val DEFAULT = Ipify
fun get(
name: String,
): IpService {
return when (name) {
IPIFY -> Ipify
else -> throw Exception()
}
}
}

View File

@@ -1,23 +1,23 @@
package com.infendro.ddns.ip.ipify
import com.infendro.ddns.Http
import com.infendro.ddns.http
import com.infendro.ddns.ip.IpService
import com.infendro.ddns.ip.ipify.dto.response.GetIpResponse
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.call.*
import io.ktor.client.request.*
import kotlinx.coroutines.runBlocking
object Ipify : IpService() {
object Ipify : IpService {
override val ipv4: String
get() = runBlocking {
val response = Http.client.get("https://api.ipify.org?format=json")
val response = http.get("https://api.ipify.org?format=json")
val body = response.body<GetIpResponse>()
body.ip
}
override val ipv6: String
get() = runBlocking {
val response = Http.client.get("https://api64.ipify.org?format=json")
val response = http.get("https://api6.ipify.org?format=json")
val body = response.body<GetIpResponse>()
body.ip
}